CLASS 09 · 2026-03-04 · 类型与函数式
相等性看似简单,实则暗藏陷阱。本讲学习等价关系的三条性质,区分引用相等与值相等,理解不可变类型与可变类型在相等性上的本质差异。
相等性操作必须是一个等价关系(equivalence relation),满足三条性质:
任何相等性定义如果违反了这三条中的任何一条,就会产生微妙的 bug。
对于不可变类型,有两种定义相等性的方式:
对于不可变类型,这两种定义是等价的。
在 TypeScript 中,=== 是引用相等。对于对象,=== 只在两个引用指向同一对象时为 true。要实现值相等,需要自定义 equalValue() 方法。
class Rational {
constructor(
private readonly numer: number,
private readonly denom: number
) {}
equalValue(other: Rational): boolean {
return this.numer === other.numer && this.denom === other.denom;
}
}
const a = new Rational(1, 2);
const b = new Rational(1, 2);
const c = a;
console.log(a === b); // false:不同对象(引用不等)
console.log(a.equalValue(b)); // true:表示同一个值
console.log(a === c); // true:同一对象
定义相等性时必须小心维护等价关系的三条性质。例如,如果相等性依赖于可变状态,传递性就可能被破坏:对象 A 和 B 此刻相等,B 和 C 此刻相等,但 A 和 C 可能不等。
可变类型有两种不同的相等性概念:
观察相等只关心当前状态;行为相等还要求未来所有操作都产生相同结果。对于可变类型,行为相等通常意味着它们是同一个对象(引用相等)。
TypeScript 的 assert.deepStrictEqual() 为内置集合提供了观察相等:递归比较每个元素,而不要求是同一对象。
哈希函数必须与相等性一致:如果 a.equals(b) 为真,则 a.hashCode() === b.hashCode() 必须为真。否则,哈希表(Map/Set)会行为异常——相等的对象被放进不同的桶,导致查找失败。
等价关系自反、对称、传递——相等性必须满足这三条。
引用 vs. 值=== 是引用相等;值相等需自定义。
可变类型观察相等(此刻)vs. 行为相等(永远)。