ES6 Class 本身不提供不可变性,但可通过构造时冻结实例、禁用 setter、返回新实例等手段模拟不可变行为;需注意 Object.freeze() 不深冻嵌套对象,应确保嵌套值本身不可变。

ES6 Class 本身不直接提供不可变性,但可以通过约束构造、禁止修改、返回新实例等方式模拟不可变对象的行为。关键不是“让 class 自动不可变”,而是用 class 封装不可变的语义和操作习惯。
构造时冻结初始状态
在 constructor 中接收参数并初始化私有字段(如用 # 私有属性),然后对整个实例调用 Object.freeze(),防止后续添加、删除或重写自有属性:
class Point {
#x;
#y;
constructor(x, y) {
this.#x = x;
this.#y = y;
Object.freeze(this); // 冻结实例本身(注意:只深冻一层)
}
get x() { return this.#x; }
get y() { return this.#y; }
// 不提供 setter,也不暴露可变方法
}
⚠️ 注意:Object.freeze() 不递归冻结嵌套对象(如传入一个普通对象作为参数),若需完全深不可变,应确保所有嵌套值本身也是不可变结构(如用 Object.freeze({}) 或 Immutable.js 类型)。
用纯函数式方法返回新对象
所有“修改”操作都不改变原实例,而是返回一个全新实例。例如实现 withX()、moveBy() 等工厂式方法:
class Point {
#x;
#y;
constructor(x, y) {
this.#x = x;
this.#y = y;
Object.freeze(this);
}
get x() { return this.#x; }
get y() { return this.#y; }
withX(newX) {
return new Point(newX, this.#y);
}
moveBy(dx, dy) {
return new Point(this.#x + dx, this.#y + dy);
}
}
// 使用示例
const p1 = new Point(1, 2);
const p2 = p1.moveBy(3, 4); // p2 是新对象,p1 未变
避免暴露可变内部结构
不返回可变引用(如数组、普通对象),防止外部绕过封装修改状态:
- 不要通过 getter 返回原始数组 —— 改用 Array.from() 或 slice() 返回副本
- 不要把私有对象属性直接暴露出去 —— 若需结构化数据,返回冻结副本或只读视图
- 谨慎使用 JSON.parse(JSON.stringify()) 深拷贝(仅适用于简单数据,不支持函数、undefined、Date 等)
配合 WeakMap 实现更安全的私有状态(可选进阶)
当需要更严格的私有性和避免 freeze 对原型链的影响时,可用 WeakMap 存储私有数据,class 实例本身保持轻量且可 freeze:
const privateData = new WeakMap();
class Counter {
constructor(value = 0) {
privateData.set(this, { value });
Object.freeze(this);
}
get value() {
return privateData.get(this).value;
}
increment() {
const { value } = privateData.get(this);
return new Counter(value + 1);
}
}
这种方式下,私有数据与实例绑定,外部无法访问或篡改,同时 class 接口干净、不可变语义清晰。
不复杂但容易忽略:不可变性的核心是约定+约束,不是语法强制。用 ES6 Class 实现,重点在于封装意图、拒绝副作用、始终返回新值。


















