老梁聊全栈: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__ 三者的三角关系,整棵树的结构就一目了然了。

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

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

相关推荐
To_OC5 小时前
LC 51 N 皇后:我以为难的是回溯,结果栽在了对角线下标
javascript·算法·leetcode
不好听6135 小时前
从一行 JSX 到屏幕像素:前端开发者必须懂的浏览器渲染管线
前端
恒拓高科WorkPlus5 小时前
企业级内网即时通讯建设模型:BeeWorks内网IM四层架构
前端
前端小李子6 小时前
前端环境变量裸奔?我用 EnvShield 给它穿了件防弹衣
前端
youtootech6 小时前
HarmonyOS《柚兔学伴》项目实战25-我的页面、Web 嵌入与项目总结
前端·华为·harmonyos
小林ixn6 小时前
从零到一理解 React 父子组件通信:手写一个 Todo 应用带你彻底搞懂单向数据流
前端·javascript·react.js
醇氧8 小时前
CountDownLatch / CyclicBarrier / Semaphore 面试高频问答清单
前端·面试·职场和发展
窝子面9 小时前
手搓最简前后端协作-node
javascript·数据库
qetfw9 小时前
MXU:Tauri 2 + React 的 MaaFramework 跨平台 GUI 源码
前端·python·react.js·前端框架·开源项目·效率工具