__proto__ vs prototype:90% 的人分不清的 JavaScript 核心机制
"每个对象都有一个秘密通道,通向另一个对象。这个通道叫
__proto__,而这条通道连成的高速公路,就叫原型链。"
一、先搞清楚两个容易混淆的东西
很多初学者看到 __proto__ 和 prototype 就头大,觉得它们是同一个东西。不是的。 它们的关系就像"钥匙"和"锁"------有关联,但用途完全不同。
__proto__ |
prototype |
|
|---|---|---|
| 谁拥有它 | 所有对象都有 | 只有函数才有 |
| 它指向什么 | 指向"创建我的那个构造函数的 prototype" |
指向这个函数作为构造函数时,实例共享的那个对象 |
| 一句话概括 | "我是谁造的?" | "我造出来的东西都能用啥?" |
二、从一个故事开始
假设你开了一家奶茶店:
javascript
// 你设计了一个"奶茶配方"(构造函数)
function MilkTea(flavor) {
this.flavor = flavor;
}
// 所有奶茶共享的能力,写在 prototype 上
MilkTea.prototype.drink = function() {
console.log(`喝了一口${this.flavor}奶茶,真好喝!`);
};
MilkTea.prototype.addTopping = function(topping) {
console.log(`给${this.flavor}奶茶加了${topping},口感升级!`);
};
// 做出两杯奶茶(实例)
const pearlTea = new MilkTea('珍珠');
const taroTea = new MilkTea('芋泥');
现在来验证一下关系:
javascript
console.log(pearlTea.__proto__ === MilkTea.prototype); // true ✅
console.log(taroTea.__proto__ === MilkTea.prototype); // true ✅
console.log(MilkTea.prototype.constructor === MilkTea); // true ✅
看明白了吗?
pearlTea.__proto__→ 指向MilkTea.prototype("我是 MilkTea 造的")MilkTea.prototype→ 是一个对象,上面挂着drink和addTopping("我造的实例都能用这些方法")
三、图解:对象之间的秘密通道
javascript
pearlTea taroTea
{ flavor: '珍珠' } { flavor: '芋泥' }
│ │
│ __proto__ │ __proto__
▼ ▼
┌─────────────────────────────────────────┐
│ MilkTea.prototype │
│ { │
│ constructor: MilkTea, │
│ drink: function() { ... }, │
│ addTopping: function() { ... } │
│ } │
└─────────────────────┬───────────────────┘
│
│ __proto__
▼
┌─────────────────────────────────────────┐
│ Object.prototype │
│ { │
│ toString: function() { ... }, │
│ hasOwnProperty: function() { ... }, │
│ valueOf: function() { ... } │
│ } │
└─────────────────────┬───────────────────┘
│
│ __proto__
▼
null ← 终点站,原型链的尽头
这就是原型链! 当你访问 pearlTea.drink() 时,JavaScript 引擎的查找过程是:
pearlTea自己身上有drink吗?→ 没有- 去
pearlTea.__proto__(即MilkTea.prototype)上找 → 找到了!执行它 - 如果还没找到,继续沿
__proto__往上找Object.prototype - 如果还没找到,继续找
null→ 没有就报undefined
就像你找东西:先翻自己的口袋,再翻家里的柜子,再翻储藏室,都没有那就是没有。
四、__proto__ 的"正经写法"
__proto__ 其实是一个 getter/setter ,它在浏览器中被实现为 Object.prototype 上的属性。正经开发中,推荐用这两个方法代替:
javascript
// 设置原型
Object.setPrototypeOf(pearlTea, someOtherProto);
// 获取原型
Object.getPrototypeOf(pearlTea); // 等价于 pearlTea.__proto__
创建对象时直接指定原型,更推荐用 Object.create:
javascript
const baseTea = {
stir() {
console.log('搅拌均匀');
}
};
const greenTea = Object.create(baseTea);
greenTea.flavor = '抹茶';
greenTea.stir(); // "搅拌均匀" ------ 沿原型链找到的
console.log(Object.getPrototypeOf(greenTea) === baseTea); // true
五、函数也是对象!所以函数也有 __proto__
这是最容易让人脑壳疼的部分。记住一个铁律:
在 JavaScript 中,函数也是对象。
javascript
function Foo() {}
// 函数 Foo 也是一个对象,所以它有 __proto__
console.log(Foo.__proto__ === Function.prototype); // true
// Function.prototype 也是一个对象
console.log(Function.prototype.__proto__ === Object.prototype); // true
// Object.prototype 是终点
console.log(Object.prototype.__proto__ === null); // true
来,画一张函数的原型链全景图:
javascript
Foo(函数)
│
│ __proto__
▼
Function.prototype ← 所有函数共享的方法(call, apply, bind...)
│
│ __proto__
▼
Object.prototype ← 所有对象共享的方法(toString, hasOwnProperty...)
│
│ __proto__
▼
null
而 Foo.prototype 是另一条线:
javascript
Foo(函数)
│
│ prototype(只有函数有这个属性)
▼
Foo.prototype ← Foo 的实例的 __proto__ 指向这里
│
│ __proto__
▼
Object.prototype
│
│ __proto__
▼
null
两条不同的链! Foo.__proto__ 和 Foo.prototype 走的是完全不同的路。
六、new 关键字到底做了什么?
理解 new 的过程,原型链就彻底通了:
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
const alice = new Person('Alice');
new Person('Alice') 背后发生了 4 件事:
javascript
// 等价于以下过程:
function fakeNew(Constructor, ...args) {
// 1. 创建一个空对象
const obj = {};
// 2. 把这个对象的 __proto__ 指向构造函数的 prototype
Object.setPrototypeOf(obj, Constructor.prototype);
// 等价于 obj.__proto__ = Constructor.prototype;
// 3. 用这个对象作为 this,执行构造函数
const result = Constructor.apply(obj, args);
// 4. 如果构造函数返回了一个对象,就用那个对象;否则返回 obj
return result instanceof Object ? result : obj;
}
验证一下:
javascript
const bob = fakeNew(Person, 'Bob');
bob.sayHi(); // "Hi, I'm Bob" ✅
console.log(Object.getPrototypeOf(bob) === Person.prototype); // true ✅
七、ES6 的 class ------ 语法糖而已
ES6 的 class 写法看起来像 Java/C++,但本质上还是原型链:
javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks`);
}
}
const rex = new Dog('Rex');
rex.speak(); // "Rex barks"
上面的代码,底层等价于:
javascript
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a sound');
};
function Dog(name) {
Animal.call(this, name); // 调用父类构造函数
}
// 建立原型链:Dog.prototype → Animal.prototype → Object.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
console.log(this.name + ' barks');
};
const rex = new Dog('Rex');
rex.speak(); // "Rex barks"
原型链图:
javascript
rex(Dog 的实例)
│
│ __proto__
▼
Dog.prototype
│
│ __proto__ (通过 Object.create(Animal.prototype) 建立)
▼
Animal.prototype
│
│ __proto__
▼
Object.prototype
│
│ __proto__
▼
null
当调用 rex.speak() 时:
rex自己没有speak→ 去Dog.prototype找Dog.prototype有speak→ 找到了,执行!
如果调用 rex.hasOwnProperty('name'):
rex没有 →Dog.prototype没有 →Animal.prototype没有- →
Object.prototype有 → 找到了!
八、几个经典面试题,来检验一下
题目 1:instanceof 的本质
javascript
console.log(rex instanceof Dog); // true
console.log(rex instanceof Animal); // true
instanceof 的原理:沿着 rex.__proto__ 这条链,看 Dog.prototype 和 Animal.prototype 是否在链上。
javascript
// 模拟 instanceof
function myInstanceof(obj, Constructor) {
let proto = Object.getPrototypeOf(obj);
while (proto !== null) {
if (proto === Constructor.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
题目 2:属性遮蔽(Shadowing)
javascript
const obj = { a: 1 };
const child = Object.create(obj);
console.log(child.a); // 1(从原型链上找到的)
console.log('a' in child); // false(child 自己没有 a)
child.a = 2; // 在 child 自己身上创建了 a
console.log(child.a); // 2(自己的属性优先)
console.log(obj.a); // 1(原型上的没被修改)
delete child.a; // 删掉自己的 a
console.log(child.a); // 1(又从原型链上找到了)
原型链上的属性不会被实例修改 ------你写 child.a = 2 不会改原型,而是在 child 自己身上新建一个同名属性"遮住"原型上的。
题目 3:Function.__proto__ === Function.prototype
javascript
console.log(Function.__proto__ === Function.prototype); // true 🤯
函数 Function 自己就是由自己创建的。 这是 JavaScript 的一个自举(bootstrap)设计------Function 是整个类型系统的起点,它打破了"函数由别人创建"的规则,自己指向自己。
九、总结:一张图记住一切
javascript
null
▲
│ __proto__
Object.prototype
▲ ▲
__proto__│ │ __proto__
│ │
Function.prototype Foo.prototype
▲ ▲
__proto__│ │ __proto__
│ │
Foo ─────────────┘
(Foo.prototype 指向右边)
实例:
foo.__proto__ → Foo.prototype → Object.prototype → null
三条核心规则:
- 所有对象都有
__proto__,它指向创建该对象的构造函数的prototype - 只有函数有
prototype,它是一个对象,用于存放实例共享的属性和方法 - 查找属性时沿
__proto__链向上搜索 ,直到找到或到达null为止
理解了这三点,原型链就彻底通了。
十、最后的灵魂拷问
javascript
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
试着画出 Object 和 Function 之间互相纠缠的原型链图吧------如果你能画对,说明你真的懂了。
记住:JavaScript 没有类,只有对象。原型链就是对象之间的"关系网",
__proto__是网线,prototype是网线另一头的插座。