CLASS 09 · 2026-03-04 · 类型与函数式

等价性

相等性看似简单,实则暗藏陷阱。本讲学习等价关系的三条性质,区分引用相等与值相等,理解不可变类型与可变类型在相等性上的本质差异。

等价关系

相等性操作必须是一个等价关系(equivalence relation),满足三条性质:

任何相等性定义如果违反了这三条中的任何一条,就会产生微妙的 bug。

不可变类型的相等性

对于不可变类型,有两种定义相等性的方式:

对于不可变类型,这两种定义是等价的。

引用相等 vs. 值相等

在 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. 行为相等(永远)。