系列全栈工程师成长难度进阶阅读约 8 分钟上一篇文章聊了 JS 四大核心概念。本文聚焦其中最反直觉的一个——原型链把它的运作机制一次讲透。很多 Java 开发者刚接触原型链时都会困惑为什么没有 class 也能继承 答案藏在 JS 的设计哲学里——JS 的继承不是复制而是委托一、一句话理解原型链当你访问obj.foo时JS 引擎的查找路线从头到尾只有 4 步1. 检查 obj 自身有没有 foo2. 没有 → 检查obj.__proto__指向的对象原型3. 还没有 → 检查原型的原型Object.prototype4. 最终 →null返回undefined这个一级级向上找的链路就是原型链。为什么这样设计优势说明内存效率方法存在原型上100 个实例共享同一份而非各自拷贝动态扩展运行时修改原型所有实例立即生效灵活组合可以混入多个对象的特性mixin 模式二、核心三角constructor · prototype · __proto__这是最容易搞混的三个概念一张表说清楚概念属于谁指向什么获取方式prototype构造函数拥有原型对象存共享方法Foo.prototype__proto__实例拥有构造函数的 prototype用Object.getPrototypeOfconstructor原型对象拥有回指构造函数Foo.prototype.constructor用一段代码一次性看清三者关系function Person(name) { this.name name; // 实例属性 } Person.prototype.greet function() { // 原型方法 return Hello, Im ${this.name}; }; const alice new Person(Alice); // 验证三角关系 console.log(alice.__proto__ Person.prototype); // true console.log(Person.prototype.constructor Person); // true console.log(alice.constructor Person); // true 理解了这个三角原型链就懂了 80%。三、属性查找与覆盖规则3.1 同名属性的遮蔽效应实例属性优先级高于原型上的同名属性Person.prototype.type human; const bob new Person(Bob); bob.type developer; // 在实例上新建了 type console.log(bob.type); // developer实例遮蔽原型 console.log(Person.prototype.type); // human原型没变 delete bob.type; console.log(bob.type); // human遮蔽层移除原型浮上来这是面试最爱考的点——赋值在实例上新建属性删除则让原型属性重新暴露。3.2 动态修改原型的即时生效const alice new Person(Alice); // 创建实例后才添加方法——依然能访问到 Person.prototype.sayBye function() { return ${this.name} says goodbye; }; alice.sayBye(); // Alice says goodbye ← 原型引用是活的 思考题如果你把Person.prototype整个替换成一个新对象已创建的实例会受影响吗为什么四、用原型链实现继承4.1 经典三步法// 父类 function Vehicle(make) { this.make make; } Vehicle.prototype.start function() { return ${this.make} starting...; }; // 子类 function Car(make, model) { Vehicle.call(this, make); // 步骤1继承实例属性 this.model model; } Car.prototype Object.create(Vehicle.prototype); // 步骤2 Car.prototype.constructor Car; // 步骤3 Car.prototype.drive function() { return ${this.make} ${this.model} is driving; }; 三步法口诀call继承属性 →Object.create继承方法 → 修复constructor。4.2 ES6 class 等价写法class Vehicle { constructor(make) { this.make make; } start() { return ${this.make} starting...; } } class Car extends Vehicle { constructor(make, model) { super(make); // 等价于 Vehicle.call(this, make) this.model model; } drive() { return ${this.make} ${this.model} is driving; } } class 没有改变任何底层机制——它只是让原型链的代码看起来更像 Java/C 风格。五、性能陷阱与最佳实践5.1 三个常见坑陷阱后果避免方式修改Object.prototype影响全局所有对象永远不要动内置原型for...in遍历到原型属性出现预期外的属性加hasOwnProperty过滤过长的原型链属性查找变慢链深度控制在 3 层以内5.2 最佳实践清单优先用class语法避免手动操作prototype用Object.create(proto)代替__proto__读原型用Object.getPrototypeOf(obj)区分自有与继承属性hasOwnProperty(key)方法在原型上数据在构造函数内六、总结核心概念一句话原型链本质对象通过__proto__向上委托查找属性prototype构造函数上的共享空间存方法__proto__实例指向构造函数 prototype 的内部链接继承实现call Object.create 修复 constructorclass原型链的语法糖底层依然是委托关系原型链是 JS 对象系统的根基。它理解起来像一棵树——刚开始只见枝节等你看清 constructor、prototype、__proto__三者的三角关系整棵树的结构就一目了然了。讨论区你在面试中遇到过哪些原型链相关的题补充一道你被问过的最刁钻的题评论区一起拆解
老梁聊全栈:JavaScript 原型链深入探索对象继承的奥秘
系列全栈工程师成长难度进阶阅读约 8 分钟上一篇文章聊了 JS 四大核心概念。本文聚焦其中最反直觉的一个——原型链把它的运作机制一次讲透。很多 Java 开发者刚接触原型链时都会困惑为什么没有 class 也能继承 答案藏在 JS 的设计哲学里——JS 的继承不是复制而是委托一、一句话理解原型链当你访问obj.foo时JS 引擎的查找路线从头到尾只有 4 步1. 检查 obj 自身有没有 foo2. 没有 → 检查obj.__proto__指向的对象原型3. 还没有 → 检查原型的原型Object.prototype4. 最终 →null返回undefined这个一级级向上找的链路就是原型链。为什么这样设计优势说明内存效率方法存在原型上100 个实例共享同一份而非各自拷贝动态扩展运行时修改原型所有实例立即生效灵活组合可以混入多个对象的特性mixin 模式二、核心三角constructor · prototype · __proto__这是最容易搞混的三个概念一张表说清楚概念属于谁指向什么获取方式prototype构造函数拥有原型对象存共享方法Foo.prototype__proto__实例拥有构造函数的 prototype用Object.getPrototypeOfconstructor原型对象拥有回指构造函数Foo.prototype.constructor用一段代码一次性看清三者关系function Person(name) { this.name name; // 实例属性 } Person.prototype.greet function() { // 原型方法 return Hello, Im ${this.name}; }; const alice new Person(Alice); // 验证三角关系 console.log(alice.__proto__ Person.prototype); // true console.log(Person.prototype.constructor Person); // true console.log(alice.constructor Person); // true 理解了这个三角原型链就懂了 80%。三、属性查找与覆盖规则3.1 同名属性的遮蔽效应实例属性优先级高于原型上的同名属性Person.prototype.type human; const bob new Person(Bob); bob.type developer; // 在实例上新建了 type console.log(bob.type); // developer实例遮蔽原型 console.log(Person.prototype.type); // human原型没变 delete bob.type; console.log(bob.type); // human遮蔽层移除原型浮上来这是面试最爱考的点——赋值在实例上新建属性删除则让原型属性重新暴露。3.2 动态修改原型的即时生效const alice new Person(Alice); // 创建实例后才添加方法——依然能访问到 Person.prototype.sayBye function() { return ${this.name} says goodbye; }; alice.sayBye(); // Alice says goodbye ← 原型引用是活的 思考题如果你把Person.prototype整个替换成一个新对象已创建的实例会受影响吗为什么四、用原型链实现继承4.1 经典三步法// 父类 function Vehicle(make) { this.make make; } Vehicle.prototype.start function() { return ${this.make} starting...; }; // 子类 function Car(make, model) { Vehicle.call(this, make); // 步骤1继承实例属性 this.model model; } Car.prototype Object.create(Vehicle.prototype); // 步骤2 Car.prototype.constructor Car; // 步骤3 Car.prototype.drive function() { return ${this.make} ${this.model} is driving; }; 三步法口诀call继承属性 →Object.create继承方法 → 修复constructor。4.2 ES6 class 等价写法class Vehicle { constructor(make) { this.make make; } start() { return ${this.make} starting...; } } class Car extends Vehicle { constructor(make, model) { super(make); // 等价于 Vehicle.call(this, make) this.model model; } drive() { return ${this.make} ${this.model} is driving; } } class 没有改变任何底层机制——它只是让原型链的代码看起来更像 Java/C 风格。五、性能陷阱与最佳实践5.1 三个常见坑陷阱后果避免方式修改Object.prototype影响全局所有对象永远不要动内置原型for...in遍历到原型属性出现预期外的属性加hasOwnProperty过滤过长的原型链属性查找变慢链深度控制在 3 层以内5.2 最佳实践清单优先用class语法避免手动操作prototype用Object.create(proto)代替__proto__读原型用Object.getPrototypeOf(obj)区分自有与继承属性hasOwnProperty(key)方法在原型上数据在构造函数内六、总结核心概念一句话原型链本质对象通过__proto__向上委托查找属性prototype构造函数上的共享空间存方法__proto__实例指向构造函数 prototype 的内部链接继承实现call Object.create 修复 constructorclass原型链的语法糖底层依然是委托关系原型链是 JS 对象系统的根基。它理解起来像一棵树——刚开始只见枝节等你看清 constructor、prototype、__proto__三者的三角关系整棵树的结构就一目了然了。讨论区你在面试中遇到过哪些原型链相关的题补充一道你被问过的最刁钻的题评论区一起拆解