系列: 全栈工程师成长难度: 进阶**阅读:**约 8 分钟
上一篇文章聊了 JS 四大核心概念。本文聚焦其中最"反直觉"的一个------原型链,把它的运作机制一次讲透。
很多 Java 开发者刚接触原型链时都会困惑:"为什么没有 class 也能继承?" 答案藏在 JS 的设计哲学里------JS 的继承不是"复制",而是"委托"
一、一句话理解原型链
当你访问 obj.foo 时,JS 引擎的查找路线从头到尾只有 4 步:
-
检查 obj 自身有没有 foo?
-
没有 → 检查
obj.__proto__指向的对象(原型) -
还没有 → 检查原型的原型(
Object.prototype) -
最终 →
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__ 三者的三角关系,整棵树的结构就一目了然了。
💬 讨论区 :你在面试中遇到过哪些原型链相关的题?
补充一道你被问过的最刁钻的题,评论区一起拆解!