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

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

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

相关推荐
格子软件3 小时前
2026年GEO优化系统源码级状态机与多模型调度拆解
java·前端·vue.js·人工智能·vue·geo
HUMHSX4 小时前
Vue 项目启动全流程解析:从入口文件到全局指令注册与页面渲染
前端·javascript·vue.js
有颜有货4 小时前
PMC生产排产的4种算法,一次讲清
java·服务器·前端
小虎牙0074 小时前
Android kotlin图片库Coil源码详解
android·前端
随风一样自由4 小时前
【前端领域】前端开发核心应用场景与落地实践
前端·前端框架
an317424 小时前
弹窗数据流设计的两种高阶架构实践
前端·vue.js·架构
谢尔登5 小时前
【React】 状态管理方案
前端·react.js·前端框架
用户938515635075 小时前
手把手教你实现一个 MCP 文件读取服务器:从协议到代码的深度解析
javascript·人工智能
用户2136610035725 小时前
Vue商品详情与放大镜组件
前端·javascript
半个落月5 小时前
从Tapas小Demo理清localStorage、事件与this
前端·javascript