javascript继承的本质是通过[[prototype]]隐式原型链实现属性按需查找,共享源于引用类型在原型上的同一内存地址,控制关键在于区分只读配置放原型、可变引用在构造函数中初始化。

JavaScript 继承的本质,是通过对象的 [[Prototype]](隐式原型)链接,形成一条向上的查找路径——也就是原型链。属性共享不是“主动分配”,而是“按需查找”:当实例自身没有某个属性时,引擎自动沿 __proto__ 向上找,直到找到或抵达 null。
属性查找机制决定共享行为
每次读取或写入属性,都触发一次原型链遍历:
- 先查实例自身(
this上定义的自有属性) - 没找到,就查
this.__proto__(即构造函数的prototype) - 仍没找到,继续查
this.__proto__.__proto__,依此类推 - 终点是
Object.prototype,其__proto__为null
因此,只要属性存在于原型对象上(比如 Parent.prototype.colors = []),所有继承它的实例都会访问到**同一个内存地址**——这就是共享的根源。
引用类型共享的典型表现
基本类型(如字符串、数字)赋值会复制值,互不影响;但引用类型(数组、对象、函数)只复制引用地址,指向同一块堆内存:
-
function Parent() { this.items = []; }→ 若写在构造函数里,每个实例独有 -
Parent.prototype.items = [];→ 所有实例共用一个数组 - 执行
c1.items.push(1)后,c2.items也会包含1
这不是缺陷,而是原型设计的自然结果:它节省内存、支持动态方法注入,但也要求开发者明确区分“实例专属”和“原型共享”场景。
如何控制共享与隔离
关键不在“能不能共享”,而在于“要不要共享”以及“由谁初始化”:
- 只读配置可放原型:
Parent.prototype.type = 'default';或Parent.prototype.defaults = Object.freeze({}) - 可变引用必须在构造函数中初始化:
function Child() { this.colors = ['red']; } - 若需继承父类实例属性,应配合
Parent.call(this, ...)(组合继承) - 用
Object.create(Parent.prototype)替代new Parent()设置子类原型,可避免父类构造逻辑意外执行
真正安全的继承,是把原型链用于方法复用,把构造函数用于实例状态初始化。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











