JavaScript原型链实现属性共享与覆盖:读取时沿__proto__链向上查找,写入时默认在实例自身创建属性遮蔽原型;修改原型上原始值或函数会影响所有未遮蔽实例,但引用类型需在构造函数中初始化以防共享污染。

JavaScript 中通过原型链实现属性的动态共享与覆盖,关键在于理解“查找优先级”和“写入行为”的差异:读取时沿原型链向上查找,写入时默认只作用于实例自身。这使得共享与覆盖可以自然共存,无需额外干预。
共享靠原型链查找
所有实例访问同一属性或方法时,若自身没有定义,就会自动沿着 __proto__ 链向上找,最终落到构造函数的 prototype 上。
- 只要没在实例上显式赋值,每次访问都走原型链,拿到的是同一份定义
- 修改原型上的函数或原始值属性(如字符串、数字),所有未遮蔽的实例立即生效
- 例如:
Person.prototype.species = 'Homo sapiens'后,p1.species和p2.species都返回该值,且p1.species === p2.species为 true
覆盖靠实例属性遮蔽
当给实例直接赋值同名属性时,JS 不会修改原型,而是在实例自身创建一个新属性——这会“遮蔽”(shadow)原型上的同名属性,后续访问优先取实例值。
-
p1.species = 'Neanderthal'后,p1.species返回新值,p2.species仍从原型读取原值 - 遮蔽是单向的:只影响当前实例,不改变原型,也不影响其他实例
- 可用
delete p1.species删除实例属性,恢复对原型属性的访问
动态修改原型要谨慎
运行时修改 prototype 是有效的,但需注意副作用和边界情况:
- 新增方法或属性,所有已有和未来实例都能访问
- 重写已有方法(如
Person.prototype.sayHello = ...),已存在实例也会立即使用新版 - 避免直接赋值整个
prototype(如Person.prototype = {...}),否则会切断已有实例的__proto__连接 - 不要修改
Object.prototype等内置原型,容易引发全局冲突
引用类型属性要特别小心
如果在 prototype 上定义数组、对象等引用类型,它们会被所有实例共享——这不是“动态覆盖”,而是意外的可变状态污染。
- 错误示例:
Person.prototype.hobbies = []→p1.hobbies.push('coding')会导致p2.hobbies也包含该项 - 正确做法:把引用类型初始化放在构造函数中(
this.hobbies = []),确保每个实例独有一份 - 若真需共享只读结构,可用
Object.freeze({})或原始值替代











