javascript中不存在原生“不可变对象继承”,关键在于避免原型链上引用类型共享:应将可变数据置于实例自身,用object.freeze保护只读配置,并通过parent.call(this)或super()确保每次新建独立副本。

原型链上避免引用类型共享
原型链继承时,如果父构造函数把数组、对象等引用类型直接挂在 this 上(比如 this.config = { timeout: 5000 }),子类所有实例会共用同一份引用,一改全改。
解决办法不是“让对象不可变”,而是不让它出现在原型链共享位置:
- 把配置类数据放在构造函数内,用
Parent.call(this)每次新建一份; - 若必须复用方法,就把可变数据剥离到实例自身,原型只放纯函数;
- 真要共享只读配置,可用
Object.freeze({ timeout: 5000 })创建冻结对象,再挂到Parent.prototype或静态属性上(注意:冻结只作用于第一层)。
构造函数继承 + 实例属性隔离
借用构造函数(Parent.call(this))天然解决引用共享问题,因为每次 new 都执行一次父构造函数,生成独立副本。
例如:
function ApiClient(base) {
this.base = base;
this.headers = Object.freeze({ 'Content-Type': 'application/json' }); // 冻结只读头
this.cache = {}; // 可变状态,但每个实例独有
}
function AuthApiClient(base, token) {
ApiClient.call(this, base); // ✅ 每个实例都有自己的 headers 和 cache
this.token = token;
}
这样即使 headers 被 freeze,也不影响子类实例行为;而 cache 是实例私有,互不干扰。
用 Object.create 定义干净原型链
别直接写 Child.prototype = new Parent(),这会让父类实例属性污染原型链。
推荐方式:
- 用
Object.create(Parent.prototype)设置原型,只继承方法,不带实例数据; - 手动修复
constructor; - 在子构造函数里调用
Parent.call(this, ...)初始化实例专属状态。
这样父类的可变属性不会被“继承下来”,整个原型链更可控、更接近“不可变语义”。
ES6 class 中的注意事项
class 是语法糖,底层仍是原型链。但 super() 必须调用,否则 this 不可用——这其实强制你在子类构造中初始化实例状态,天然规避了原型上挂可变数据的问题。
如果你希望某个配置真正不可变,可以:
- 在父类 constructor 中用
Object.freeze(this.config); - 或定义 getter 返回冻结对象,禁止 setter;
- 避免在
prototype上放可变对象,方法尽量无副作用。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











