原型继承(prototypal inheritance)的工作原理
这是一个非常常见的 JavaScript 问题。所有 JS 对象都有一个__proto__
属性,指向它的原型对象。当试图访问一个对象的属性时,如果没有在该对象上找到,它还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。这种行为是在模拟经典的继承,
1. 每个对象都有一个内部属性 [[Prototype]]
- 可以通过
__proto__
访问(不推荐) - 或通过
Object.getPrototypeOf(obj)
获取 - 或通过
Object.setPrototypeOf(obj, proto)
设置
2. 原型对象(prototype
)
- 函数对象有一个
.prototype
属性,它用于构建新对象的[[Prototype]]
constructor
创建的新对象,其__proto__
指向该构造函数的prototype
当你访问一个对象的属性时,例如:
obj.name
JS 引擎的执行顺序如下:
-
当前对象
obj
中查找name
属性。 -
如果没有,就查找其原型对象
obj.__proto__
中是否有name
。 -
如果还没有,就继续沿着原型链向上查找,直到:
- 找到为止(返回值)
- 原型链终点
null
(返回undefined
)
📚 构造函数 + 原型链的组合方式(经典原型继承)
function Animal(name) {this.name = name;
}Animal.prototype.sayHi = function() {console.log("Hi, I'm " + this.name);
};const cat = new Animal("Kitty");cat.sayHi(); // Hi, I'm Kitty
cat.__proto__ === Animal.prototype
✅Animal.prototype.__proto__ === Object.prototype
✅
这条链一直延伸到 null
终点。
为什么 JavaScript 要设计“原型链继承”机制? 我的另一种思考…
✨ 1. 动态语言需要灵活的继承模型
JavaScript 是一种动态语言,强调:
- 对象可以随时添加/删除属性
- 类和实例的界限模糊
- 对象结构可随运行时变化
➡️ 传统的基于类(Class-based)继承 结构较为死板、静态,而 JS 初衷是用于浏览器脚本,需要灵活、轻量、动态。
原型继承天然支持:
const a = { greeting: 'hi' };
const b = Object.create(a); // b 继承自 a
无需类、构造函数,只要对象就能继承对象,非常轻便。
🧱 2. 万物皆对象,继承也要“对象化”
不像 Java/C++ 使用 class 来定义继承,JavaScript 一切都可以看作是对象(包括函数、数组等)。
- JavaScript 设计者 Brendan Eich 的理念是: “对象应该能继承另一个对象” 。
- 所以不需要 class 和 instance 的强制约束 —— 只需链式对象(对象 → 原型对象 → 更高层原型…)。
obj --> obj.__proto__ --> obj.__proto__.__proto__ --> null
这就形成了所谓的 原型链(Prototype Chain)。
🧬 3. 共享行为、节省内存
多个对象可以共享一个原型中的方法和属性,避免重复开销:
const personMethods = {greet() { console.log('Hello'); }
};const p1 = Object.create(personMethods);
const p2 = Object.create(personMethods);p1.greet(); // Hello
p2.greet(); // Hello
- 如果没有原型机制,
greet()
方法需要在每个对象上都拷贝一份(浪费内存)。 - 通过原型链,“行为”可以被多个对象复用。
🧩 4. 实现灵活的“差异化对象”结构
原型机制允许对象“有差异地”继承:
const animal = { move() {} };
const dog = Object.create(animal);
dog.bark = () => console.log('woof');
dog
是个有bark
行为的animal
- 你可以定制对象之间的差异而不破坏它们的继承结构
🚀 5. 支持动态补丁(Monkey Patching)和热修复
因为原型是对象,运行时可以随时改:
Array.prototype.sum = function() {return this.reduce((a, b) => a + b, 0);
};[1, 2, 3].sum(); // 6
JS 的生态系统(如 Polyfill、第三方补丁)大量利用了这种能力。这在 class 语言中是很难做到的。
🔄 6. 为未来引入 class
奠定基础
ECMAScript 2015 (ES6) 中加入了 class
语法,其实只是原型的语法糖:
class Person {sayHi() { console.log('hi'); }
}const p = new Person();
实际上等价于:
function Person() {}
Person.prototype.sayHi = function() { console.log('hi'); }
const p = new Person();
➡️ class
语法只是“看起来像传统 OO”,底层仍然是原型链。
参考
- https://www.quora.com/What-is-prototypal-inheritance/answer/Kyle-Simpson
- https://davidwalsh.name/javascript-objects