老梁聊全栈:JavaScript 原型链深入探索对象继承的奥秘

系列: 全栈工程师成长难度: 进阶**阅读:**约 8 分钟

上一篇文章聊了 JS 四大核心概念。本文聚焦其中最"反直觉"的一个------原型链,把它的运作机制一次讲透。

很多 Java 开发者刚接触原型链时都会困惑:"为什么没有 class 也能继承?" 答案藏在 JS 的设计哲学里------JS 的继承不是"复制",而是"委托"

一、一句话理解原型链

当你访问 obj.foo 时,JS 引擎的查找路线从头到尾只有 4 步:

  1. 检查 obj 自身有没有 foo?

  2. 没有 → 检查 obj.__proto__ 指向的对象(原型)

  3. 还没有 → 检查原型的原型(Object.prototype

  4. 最终 → null,返回 undefined

这个"一级级向上找"的链路,就是原型链。

为什么这样设计?

优势 说明
内存效率 方法存在原型上,100 个实例共享同一份,而非各自拷贝
动态扩展 运行时修改原型,所有实例立即生效
灵活组合 可以混入多个对象的特性(mixin 模式)

二、核心三角:constructor · prototype · proto

这是最容易搞混的三个概念,一张表说清楚:

概念 属于谁 指向什么 获取方式
prototype 构造函数拥有 原型对象(存共享方法) Foo.prototype
__proto__ 实例拥有 构造函数的 prototype Object.getPrototypeOf
constructor 原型对象拥有 回指构造函数 Foo.prototype.constructor

用一段代码一次性看清三者关系:

复制代码
function Person(name) {
  this.name = name;                           // 实例属性
}
Person.prototype.greet = function() {         // 原型方法
  return `Hello, I'm ${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 + 修复 constructor
class 原型链的语法糖,底层依然是委托关系

原型链是 JS 对象系统的根基。 它理解起来像一棵树------刚开始只见枝节,等你看清 constructor、prototype、__proto__ 三者的三角关系,整棵树的结构就一目了然了。

💬 讨论区 :你在面试中遇到过哪些原型链相关的题?

补充一道你被问过的最刁钻的题,评论区一起拆解!

相关推荐
智码看视界1 小时前
老梁聊全栈系列 JavaScript语言本质:从原型链到异步编程的深度解析
开发语言·javascript·全栈·javascript核心
布朗克1682 小时前
39 Spring Boot Web实战
前端·spring boot·后端·实战
纽格立科技2 小时前
DRM 发射端链路图(上)
前端·人工智能·车载系统·信息与通信·传媒
云水一下2 小时前
Vue.js从零到精通系列(七):高级特性实战——Teleport、异步组件、自定义指令与TypeScript深度结合
前端·vue.js·typescript
qq4356947012 小时前
Vue05
前端·vue.js
qq_422152572 小时前
PDF 解密工具怎么选?2026 年文档密码移除方案与注意事项
java·前端·pdf
YHHLAI2 小时前
前端工程化调用 AI 多模态生图模型:Qwen Image Demo 实战
前端·人工智能
触底反弹2 小时前
一文彻底搞懂 JavaScript 栈和队列(建议收藏)
javascript·算法·面试
To_OC2 小时前
我一直以为 Ajax 是个黑盒,直到我写了这 50 行代码
前端·后端·全栈