JavaScript 的秘密生活:理解原型
Source: Dev.to
对象是委托的,它们不复制
Timothy 带着笔记本电脑和困惑的表情来到图书馆。他一直在阅读 JavaScript 继承的内容,却发现有些地方说不通。
Timothy: “我学到 JavaScript 使用原型(prototype)实现继承。但我也知道 JavaScript 现在有类(class)了。那么到底是哪一种?JavaScript 用原型还是类?”
Margaret: “两者都有,也都不是。情况比这更微妙。”
什么是继承?
Timothy: “子类从父类继承时,会得到父类的所有属性和方法。”
Margaret: “在 Python、Java 等语言里是这样的。子类会复制或包含父类的行为。而 JavaScript 则使用 委托(delegation)。”
委托 vs. 复制
对象不是把属性复制下来,而是指向其他对象并说:“如果我这里没有你要的,就去那个对象那儿找。”
简单示例
const parent = {
greet: function() {
console.log("Hello, " + this.name);
}
};
const child = Object.create(parent);
child.name = "Alice";
child.greet(); // "Hello, Alice"
Object.create(parent)创建了一个新对象,其隐藏的[[Prototype]]链接指向parent。- 当调用
child.greet()时,JavaScript 会先在child上查找greet。未找到 → 再去child的原型(parent)上查找。找到后以child为this调用它。
多个对象共享同一原型
const parent = {
greet: function() {
console.log("Hello, " + this.name);
}
};
const alice = Object.create(parent);
alice.name = "Alice";
const bob = Object.create(parent);
bob.name = "Bob";
alice.greet(); // "Hello, Alice"
bob.greet(); // "Hello, Bob"
alice 和 bob 都委托给同一个 parent 对象,但 greet 中的 this 指向调用者。
[[Prototype]] vs. .prototype
隐藏的链接 [[Prototype]]
const obj = Object.create(parent);
// obj.[[Prototype]] points to parent (invisible)
函数上的 .prototype 属性
function User(name) {
this.name = name;
}
// User.prototype is a regular property on the User function
console.log(User.prototype); // { constructor: User }
使用 new
function User(name) {
this.name = name;
}
User.prototype.greet = function() {
console.log("Hello, " + this.name);
};
const alice = new User("Alice");
alice.greet(); // "Hello, Alice"
当执行 new User("Alice") 时,JavaScript 会:
- 创建一个全新的对象。
- 将该对象的
[[Prototype]]设置为User.prototype。 - 用新对象作为
this调用User构造函数。 - 返回这个新对象。
你可以验证这层链接:
console.log(Object.getPrototypeOf(alice) === User.prototype); // true
Object.getPrototypeOf()(或旧式的 __proto__)可以显示看不见的原型链。
类:原型的语法糖
前面例子的类写法
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log("Hello, " + this.name);
}
}
const alice = new User("Alice");
alice.greet(); // "Hello, Alice"
- 类语法在内部创建了相同的原型链。
- 在类内部定义的方法(
greet)会放到User.prototype上。 new User()仍然会创建一个对象,其[[Prototype]]指向User.prototype。
为什么叫“语法糖”?
这个术语指的是语法看起来不同且通常更清晰,但底层机制——原型委托——保持不变。类还提供了 extends、super() 等便利关键字,帮助你处理原型链,但它们并没有引入全新的继承模型。
小结
- JavaScript 对象通过隐藏的
[[Prototype]]链接 委托 给其他对象。 - 构造函数上的
.prototype属性是为使用new创建的对象设置这条隐藏链接的一种方式。 Object.getPrototypeOf()(或__proto__)可以让你检查原型链。- ES6 类只是写同样基于原型的模式的更简洁方式;它们并没有取代原型。