skip to content
月与羽

原型

/ 8 min read

好的,这是一个在 JavaScript 中非常经典且容易混淆的问题。我们来用清晰的类比和代码,彻底讲清楚 __proto__prototype[[Prototype]] 三者的区别和联系。

一句话总结

  • prototype:是 函数 独有的属性,它是一个对象。它的作用是为这个函数通过 new 创建出来的实例 提供共享的属性和方法。可以把它想象成一个“蓝图”或“模具”。
  • __proto__:是 实例对象 独有的属性,它也是一个对象。它指向创建该实例的构造函数的 prototype。它就是实例与“蓝图”之间的 连接
  • [[Prototype]]:是 ECMAScript 规范中定义的 内部属性。它才是对象原型链的实际链接,对外不可见。__proto__ 属性是浏览器为了方便访问 [[Prototype]] 而实现的一个非标准(但后来被标准化)的访问器。

详细解析

我们用一个“造车”的例子来贯穿整个解释。

1. prototype (原型对象)

  • 拥有者构造函数 (Constructor Function)
  • 是什么:一个普通的对象。
  • 作用:作为该构造函数创建的所有实例的“共享仓库”。所有实例都可以访问到这个 prototype 对象上的属性和方法。

代码示例:

// 1. 定义一个“汽车”构造函数 (可以看作是“汽车设计图纸”的工厂)
function Car(brand) {
this.brand = brand;
}
// 2. 在“汽车设计图纸”(Car.prototype)上添加一个所有汽车都具备的功能
Car.prototype.honk = function() {
console.log('Beep beep!');
};
// 3. Car.prototype 是一个对象,它有 honk 方法和一个默认的 constructor 属性
console.log(Car.prototype);
// 输出: { honk: [Function (anonymous)], constructor: [Function: Car] }
// 注意:普通对象和箭头函数没有 prototype 属性
let obj = {};
const arrowFunc = () => {};
console.log(obj.prototype); // undefined
console.log(arrowFunc.prototype); // undefined

类比Car 是一个“汽车工厂”。Car.prototype 就是一张设计蓝图。我们在这张蓝图上规定了,所有根据这张蓝图造出来的车,都应该有按喇叭(honk)的功能。


2. [[Prototype]]__proto__ (原型链接)

  • 拥有者每一个 JavaScript 对象实例
  • 是什么:一个指向其“原型对象”的内部链接。
  • 作用:当试图访问一个对象的某个属性时,如果该对象本身没有这个属性,JavaScript 引擎就会通过 [[Prototype]] 链接,去它的原型对象上查找,如果还没有,就继续沿着原型对象的 [[Prototype]] 向上查找,直到找到或者到达原型链的终点 (null)。这个过程就是 原型链

__proto__[[Prototype]] 的一个非标准但广泛实现的 访问器。你可以通过它来读取或设置一个对象的 [[Prototype]]

代码示例(接上例):

// 4. 使用 new Car() “生产”两辆具体的汽车实例
let myCar1 = new Car('Toyota');
let myCar2 = new Car('Honda');
// myCar1 本身没有 honk 方法
console.log(myCar1.hasOwnProperty('honk')); // false
// 5. 但 myCar1 可以调用 honk 方法,因为它通过 __proto__ 找到了 Car.prototype
myCar1.honk(); // Beep beep!
// 6. 核心关系:实例的 __proto__ 指向其构造函数的 prototype
console.log(myCar1.__proto__ === Car.prototype); // true
console.log(myCar2.__proto__ === Car.prototype); // true
// 现代、标准的访问方式是使用 Object.getPrototypeOf()
console.log(Object.getPrototypeOf(myCar1) === Car.prototype); // true
myCar1.__proto__ === myCar1.constructor.prototype;

类比myCar1 是一辆具体的丰田车。这辆车出厂时,车内有一个隐藏的标签[[Prototype]],可以通过 __proto__ 查看),上面写着:“我是根据‘汽车设计蓝图’(Car.prototype)制造的”。

当你让 myCar1 按喇叭(myCar1.honk()),它先在自己身上找“喇叭按钮”,发现没有。于是它查看那个隐藏标签,找到了“设计蓝图” Car.prototype,发现蓝图上有 honk 的说明,于是成功按响了喇叭。


三者关系图

这张图能帮你把所有关系串联起来:

+-------------------+ +----------------------+
| 构造函数 | | 实例对象 |
| function Car | | let myCar1 |
+-------------------+ +----------------------+
| prototype |------->| |
+-------------------+ | brand: "Toyota" |
| |
| __proto__ / |
| [[Prototype]] |
+--------|-------------+
|
|
+------------------------|-----------------+
| 原型对象 |
| Car.prototype |
+------------------------------------------+
| constructor: (指向 Car 构造函数) |
| honk: function() { ... } |
| |
| __proto__ / [[Prototype]] (指向 Object.prototype) |
+------------------------------------------+
  1. Car 这个函数有一个 prototype 属性,指向它的原型对象。
  2. myCar1 这个实例对象有一个 __proto__ 内部属性,也指向 Car 的原型对象。
  3. 因此,myCar1.__proto__Car.prototype 指向的是同一个对象
  4. Car.prototype 本身也是一个对象,所以它也有自己的 __proto__,指向 Object.prototype。这就是原型链的延续。

总结与最佳实践

特性prototype__proto__ / [[Prototype]]
谁拥有?构造函数 (e.g., Function, Array, Car)几乎所有对象实例 (e.g., {}, [], new Car())
是什么?一个对象,作为共享属性的“蓝图”或“模板”。一个指向原型对象的内部链接
作用定义所有实例共享的方法和属性。构建原型链,实现继承。
如何访问直接通过 Constructor.prototype 访问。推荐: Object.getPrototypeOf(obj)
不推荐: obj.__proto__
graph TD
A[Function] -->|__proto__| B[Function.prototype]
B -->|__proto__| C[Object.prototype]
C -->|__proto__| D[null]
E[Object] -->|__proto__| B
F[Array] -->|__proto__| B
G[所有其他函数] -->|__proto__| B

所有函数的 proto 都是Function.prototype

普通对象(通过 {} 或 new Object() 创建的) proto 是Object.prototype

Object.prototype的 proto 是 null

总结表格

操作属性位置行为结果
读取 (Get)自身直接返回自身属性值自身的属性值
读取 (Get)原型链沿原型链向上查找并返回第一个找到的值原型链上的属性值
读取 (Get)自身 & 原型链自身属性遮蔽原型属性,返回自身属性值自身的属性值
读取 (Get)不存在查找到原型链末端(null)undefined
写入 (Set)自身直接修改自身属性值自身属性被更新
写入 (Set)不在自身,原型上有(可写)在自身创建新属性(遮蔽)实例上创建了一个同名的新属性
写入 (Set)不在自身,原型上有(只读)写入操作失败静默失败或(严格模式下)抛出TypeError
写入 (Set)不在自身,原型上有(Setter)调用原型上的 setter,this 指向实例setter 函数被执行,通常会在实例上创建/修改属性