核心是避免把可变状态放在原型上——因原型属性被所有实例共享,引用类型修改会导致相互影响;正确做法是在构造函数内用this初始化引用类型,原型只放方法和不可变值。

用原型链解决对象共享状态错乱,核心不是“用原型链去修”,而是**避免把可变状态放在原型上**——因为原型上的属性会被所有实例共享,一旦修改,就会相互影响。
问题根源:原型上定义了引用类型属性
很多人误以为“用 new 创建对象就天然隔离状态”,但若在构造函数外、原型上直接挂了数组、对象、日期等引用类型,所有实例会共用同一份内存:
错误示例:
function User() {}
User.prototype.cart = []; // ❌ 所有用户共用同一个购物车数组
User.prototype.profile = { name: '' }; // ❌ 共用同一个 profile 对象
这时 user1.cart.push('book') 会导致 user2.cart 也多出 'book'。
立即学习“Java免费学习笔记(深入)”;
正确做法:状态初始化移到构造函数内
确保每个实例拥有自己独立的属性副本:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 在
constructor或构造函数体内,用this.xxx = []、this.xxx = {}初始化引用类型 - 原型上只放**方法**(函数)和**不可变值**(如字符串常量、数字、布尔值)
正确示例:
function User(name) {
this.name = name;
this.cart = []; // ✅ 每个实例独享
this.profile = {}; // ✅ 独享空对象,后续可赋值
}
User.prototype.addToCart = function(item) {
this.cart.push(item); // ✅ 方法操作的是 this.cart(实例自己的)
};
User.prototype.getName = function() {
return this.name; // ✅ 访问实例自有属性
};
进阶:需要共享逻辑但隔离状态?用闭包或 WeakMap
极少数场景需在方法中缓存计算结果,又不想污染实例属性。可用 WeakMap 实现私有状态映射:
const cartCache = new WeakMap();
function User(name) {
this.name = name;
}
User.prototype.getCartSummary = function() {
if (!cartCache.has(this)) {
cartCache.set(this, { count: this.cart.length, total: 0 });
}
return cartCache.get(this);
};
这样状态仍隔离,且不暴露在 this 上,也不会被遍历到。
ES6 Class 写法同样适用该原则
Class 只是语法糖,本质仍是基于原型。以下写法依然危险:
class User {
cart = []; // ❌ 类字段(非在 constructor 内)在某些环境下仍可能被共享(尤其 Babel 转译旧版本)
static config = {}; // ❌ static 属于类本身,更是全类共享
}
推荐写法:
class User {
constructor(name) {
this.name = name;
this.cart = []; // ✅ 明确在构造时创建
}
addToCart(item) {
this.cart.push(item);
}
}
不复杂但容易忽略:状态归属决定隔离性。原型是共享的通道,实例才是状态的容器。

















