在深入学习原型链之前,必须先把两个容易混淆的概念彻底分清:prototype(原型对象)和 proto(对象内部的原型链接)。它们是原型机制的一体两面,分别从“构造函数”和“实例对象”两个角度来暴露同一个东西。
一、它们到底是什么
1. prototype —— 函数的属性
prototype 是函数对象独有的属性。每个普通函数(非箭头函数)在创建时,都会自动获得一个 prototype 属性,指向一个对象。这个对象就是将来由该函数构造出来的实例的原型。
function Person(name) {
this.name = name;
}
console.log(Person.prototype); // { constructor: Person }
Person.prototype 是一个普通对象,里面默认只有一个 constructor 属性,指回函数自身。你可以向这个对象添加方法或属性,将来所有通过 new Person() 创建的实例都能共享它们:
Person.prototype.sayHi = function() {
console.log('Hi, I am ' + this.name);
};
const p1 = new Person('Alice');
p1.sayHi(); // Hi, I am Alice
2. proto —— 对象的隐藏链接
proto 是一个访问器属性,存在于几乎所有对象上,指向该对象继承自的原型对象。它并不是 ECMAScript 标准推荐的访问方式(早期甚至是非标准特性),标准中推荐使用 Object.getPrototypeOf() 和 Object.setPrototypeOf() 来操作原型链。
console.log(p1.__proto__ === Person.prototype); // true
p1 是由 Person 构造函数生成的实例,它的 proto 就指向 Person.prototype。当我们调用 p1.sayHi() 时,JavaScript 先在 p1 自身属性中查找 sayHi,找不到就沿着 p1.proto 去找,也就是去 Person.prototype 上找,从而成功调用。
二、核心区别总结
| 对比维度 | prototype | proto |
|---------|-------------|------------|
| 归属 | 函数对象拥有的属性 | 几乎所有对象都有的属性(除 null、Object.create(null) 等) |
| 作用 | 定义由该构造函数创建的实例的原型 | 建立对象的原型链连接,指向其原型对象 |
| 存在关系 | 构造函数用 .prototype 去“提供”原型 | 实例用 .proto 去“使用”原型 |
| 标准访问 | 直接使用 | 推荐 Object.getPrototypeOf() / Object.setPrototypeOf() |
用一张图来帮助记忆:
构造函数 Person ─── prototype ──→ Person.prototype 对象
↑
实例 p1 ─── __proto__ ──────┘
- 构造函数通过
prototype定义“模板”; - 实例通过
proto链接到“模板”。
三、为什么容易混淆
两个属性经常被混为一谈,是因为很多教程说“对象的原型”,实际上指向的是同一个东西:
// 对于实例 p1,下面三个表达式指向同一个对象
p1.__proto__ === Person.prototype
Object.getPrototypeOf(p1) === Person.prototype
p1.constructor.prototype === Person.prototype // 除非 constructor 被修改过
但是注意,函数本身也有 proto,因为函数也是对象,它由 Function 构造函数创建,所以:
console.log(Person.__proto__ === Function.prototype); // true
这属于函数自身的原型链,和它作为构造函数用的 prototype 没有任何关系。理解这一点,可以在后续学习原型链完整查找机制时少走弯路。
四、实用建议
- 永远不要在代码中直接使用
proto,它已被标准定义为可选特性,且性能不如标准方法,部分环境可能隐藏该属性。请始终使用Object.getPrototypeOf(obj)获取原型,用Object.setPrototypeOf(obj, proto)修改原型。 - 给构造函数添加共享方法时,挂在
Constructor.prototype上,而不是直接在内部用this.xxx =添加,后者会重复创建函数副本,浪费内存。 - 修改原型时务必小心,尤其是修改内置类型(如
Array.prototype)的原型,可能带来难以发现的兼容性问题。
掌握了 prototype(构造函数的“模板”)和 proto(实例通往模板的“桥梁”)的区别,就为理解原型链、继承原理打下坚实基础。