原型链是 JS 面向对象的基石。3 个绕不开的问题:
prototype、__proto__、constructor有啥区别?new一个对象时,原型链是怎么建的?instanceof的原理是什么?
先看一个现象:
js
function Person() {}
Person.prototype.sayHi = function() {
return 'hi';
};
const p = new Person();
console.log(p.sayHi()); // "hi"
p 上明明没定义 sayHi,却能调用。为什么?
答:原型链。
一、原型链查找机制
访问对象属性时,JS 会这么找:
- 先在实例自身找
- 找不到 → 沿
__proto__往上找 - 一直找到
Object.prototype Object.prototype.__proto__ === null,链的尽头- 还找不到 → 返回
undefined
对着上面的例子:
js
function Person() {
this.name = '张三';
}
Person.prototype.sayHi = function() {
return 'hi';
};
const p = new Person();
console.log(p.name); // "张三" ------ 自身找到
console.log(p.sayHi()); // "hi" ------ 从 Person.prototype 找到
console.log(p.toString); // Object.prototype 上的方法
console.log(p.abc); // undefined ------ 找到 null 都没找到
查找路径:
javascript
p → Person.prototype → Object.prototype → null
这就是"原型链" 。链上的每一环,靠 __proto__ 连起来。
二、3 个核心概念
理解了查找机制,再看这 3 个概念就清晰了。
概念 1:prototype(函数才有)
每个函数 都有一个 prototype 属性,它是一个对象。
js
function Person() {}
console.log(Person.prototype);
// { constructor: Person }
作用 :通过 new 创建的所有实例,共享 prototype 上的属性和方法。
js
Person.prototype.sayHi = function() {
return 'hi';
};
const p1 = new Person();
const p2 = new Person();
p1.sayHi(); // "hi"
p2.sayHi(); // "hi" ------ 两个实例共享同一个方法
为什么设计成这样?
省内存 ------ 100 个实例不用各存一份方法,都指向 prototype 上的同一个函数。
概念 2:__proto__(所有对象都有)
每个对象 都有 __proto__ 属性(现在推荐用 Object.getPrototypeOf())。
它指向创建这个对象的构造函数的 prototype。
js
function Person() {}
const p = new Person();
console.log(p.__proto__ === Person.prototype); // true
关键 :__proto__ 就是上面查找机制里"往上找"用的那根线。
概念 3:constructor(prototype 对象上的属性)
每个 prototype 对象 都有 constructor 属性,指回构造函数本身。
js
function Person() {}
console.log(Person.prototype.constructor === Person); // true
作用:通过实例找回它的构造函数。
js
const p = new Person();
console.log(p.__proto__.constructor === Person); // true
console.log(p.constructor === Person); // true(通过原型链找到)
三、三者关系图
javascript
Person(构造函数)
│
│ prototype
▼
Person.prototype(原型对象)
│ ▲
│ │ constructor
│________│
│
│ __proto__
▼
Object.prototype
│
│ __proto__
▼
null
用 new 创建实例后:
scss
Person(构造函数)
│
│ prototype
▼
Person.prototype
▲
│ __proto__
│
p(实例) ← 通过 new 创建
核心公式:
实例.__proto__ === 构造函数.prototype构造函数.prototype.constructor === 构造函数
四、new 时原型链是怎么建的
new Fn() 时,内部会做一个关键动作:
js
const instance = Object.create(Fn.prototype);
Object.create(Fn.prototype) = 创建一个空对象,把它的 __proto__ 指向 Fn.prototype。
这就是为什么 new 出来的实例能沿原型链访问 Fn.prototype 上的方法。
new 的完整过程不熟悉可以看下 new 都干了什么。
五、instanceof 其实就是查原型链
理解了原型链查找,instanceof 就是顺手能想到的应用。
a instanceof B 判断的是:沿着 a 的原型链往上找,看有没有一环等于 B.prototype。
用之前的例子:
js
function Person() {}
const p = new Person();
console.log(p instanceof Person); // true
console.log(p instanceof Object); // true
p 的原型链:
javascript
p → Person.prototype → Object.prototype → null
Person.prototype在这条链上 →p instanceof Person为 trueObject.prototype也在这条链上 →p instanceof Object也为 true
理解了这个,手写 instanceof 就是把上面这个查找过程翻译成代码:
js
function myInstanceof(instance, Constructor) {
let proto = Object.getPrototypeOf(instance); // 相当于 instance.__proto__
while (proto) {
if (proto === Constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto); // 沿原型链往上找
}
return false;
}
测试:
js
console.log(myInstanceof(p, Person)); // true
console.log(myInstanceof(p, Object)); // true
console.log(myInstanceof(p, Array)); // false
六、几个常见误区
误区 1:Array.__proto__ === Object.prototype?
错 。Array 本身是一个函数(构造函数)。
js
console.log(Array.__proto__ === Function.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
记忆:
- 函数的
__proto__→Function.prototype - 函数的
prototype.__proto__→Object.prototype(因为 prototype 是普通对象)
误区 2:直接修改 prototype 会怎样?
js
function Person() {}
const p1 = new Person();
Person.prototype = { sayHi: () => 'hi' }; // 直接替换 prototype
const p2 = new Person();
p1.sayHi(); // 报错 ------ p1 还指向旧的 prototype
p2.sayHi(); // "hi" ------ p2 指向新 prototype
替换 prototype 不影响已创建的实例,只影响后续 new 的。
误区 3:__proto__ 是啥时候赋值的?
在 new 那一刻。之后修改构造函数的 prototype 引用,已存在的实例不受影响。
七、总结
3 个核心概念:
| 概念 | 属于谁 | 指向什么 |
|---|---|---|
| prototype | 函数 | 原型对象 |
| proto | 对象 | 构造函数的 prototype |
| constructor | prototype | 构造函数本身 |
2 条核心公式:
ini
实例.__proto__ === 构造函数.prototype
构造函数.prototype.constructor === 构造函数
查找机制:沿 __proto__ 向上,到 null 结束。