本文总结了原型以及原型链的一些概念和规律,并且提供了代码示例进行说明。有助于更深一步的理解原型与原型链
一、原型
简介
- 每个函数对象都有一个
prototype
属性,默认指向一个空的Object
实例对象(即称为原型对象) - 原型对象中有一个
constructor
属性,它指向当前的函数对象
代码示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Test() {}
Test.prototype.say = () => {
console.log('Say Hello');
}
const t1 = new Test();
const t2 = new Test();
Test.prototype.say = () => {
console.log('Say Hello');
}
console.log("原型对象:", Object.keys(Test.prototype)); // [],说明函数的原型对象是一个空对象
console.log(Test.prototype.constructor === test); // true,说明 原型对象的 constructor 属性,它指向当前的函数对象
显式原型和隐式原型
- 每个函数都有一个
prototype
属性,即显式原型 - 每个实例对象都有一个
__proto__
属性,即隐式原型 - 实例对象的隐式原型的值等于对应函数的显式原型的值
1 | function Test() {} |
显式原型和隐式原型的实现机制
- 函数的
prototype
属性:在定义函数时自动添加的,默认值是一个空的Object
对象 - 对象的
__proto__
属性:创建对象时自动添加的,默认值为构造函数的prototype
属性值 - 开发人员一般都是通过操作函数的
prototype
属性来修改原型
原型补充
函数的显式原型指向的对象默认是空
Object
对象(但是Object
除外,Object
的原型不是空对象,且没有__proto__
属性)- 所以
Object
的原型对象是原型链的尽头,Object
的原型对象的原型就是null
了1
2
3
4
5
6
7
8
9function Test() {}
const o = {};
const t = new Test();
console.log(o.__proto__); // 包含多个方法的 Object 对象,没有 __proto__ 属性
console.log(t.__proto__); // 空对象
console.log(o.__proto__.__proto__); // null
- 所以
每个函数对象既有一个隐式原型,也有一个显式原型
- 所有函数都是
Function
的实例(包括Function
自身) - 每个函数的隐式原型都是同一个实例对象
- 唯一的一个隐式原型等于显式原型的函数对象就是
Function
这个函数对象1
2
3
4
5
6
7
8
9
10
11function Test() {}
function Hello() {}
console.log(Test.prototype); // 空对象
console.log(Test.__proto__); // ƒ () { [native code] }
console.log(Hello.prototype === Test.prototype) // false
console.log(Hello.__proto__ === Test.__proto__); // true
console.log(Hello.__proto__ === Function.__proto__); // true
console.log(Function.prototype === Function.__proto__); // true
- 所有函数都是
二、原型链(别名:隐式原型链)
概念
访问一个对象的属性时
- 先在自身属性中查找,找到返回
- 如果没有,再沿着
__proto__
这条链向上查找,找到返回 - 如果最终没有找到,返回
undefined
作用:查询对象属性
对象读写
读取对象的属性时,会自动到对象的原型链中查找
1
2
3
4
5
6
7
8
9
10
11
12function Person() {}
Person.prototype.speak = () => {
console.log('Speak Chinese!');
}
const p1 = new Person();
const p2 = new Person();
// 每个实例对象都拥有函数原型中的属性/方法
p1.speak(); // Speak Chinese!
p2.speak(); // Speak Chinese!设置对象的属性时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Person() {}
Person.prototype.name = '小明';
const p1 = new Person();
const p2 = new Person();
// 读取属性时,会从当前对象以及原型链中进行查找
console.log(p1.name); // 小明
// 设置属性时,直接修改当前对象上的属性,当前对象上没有,则直接添加,不会修改原型链上的属性
p2.name = '小红';
console.log(p1.name); // 小明
console.log(p2.name); // 小红方法一般定义在原型上,属性一般通过构造函数定义在对象本身上
扩展:instanceOf
操作符原理
a instanceOf C
: 如果C
的显式原型在a
的隐式原型链上,返回true
,否则返回false
1
2
3
4
5
6
7
8
9
10
11
12function A() {};
function B() {};
function C() {}
// 将 C 的原型赋值给 B 的原型,将 B 的原型赋值给 A 的原型,这两行代码位置不能颠倒
B.prototype = C.prototype;
A.prototype = B.prototype;
const a = new A();
console.log(a instanceof C);
三、实例讲解
1、实例一
1 | function A() {} |
实例二
1 | function Test() {} |
四、总结
- 每个函数对象(
Object
函数对象除外)都默认有一个显式原型对象,指向一个空的Object
对象,空对象中有一个 constructor 属性,指向当前函数对象 - 所有函数对象都有一个隐式原型对象(包括
Function
函数对象),且都默认指向Function
函数对象的显式原型对象 - 函数对象的显式原型等于实例对象的隐式原型
访问对象属性时,会先在实例对象本身进行查找,找到则返回,没找到则在隐式原型链中继续查找,找到则返回,没找到返回
undefined
额外补充一点:函数对象的隐式原型和显示原型没有关系(
Function
函数对象除外),Function
函数对象的显式原型等于自身的隐式原型(因为所有函数都是Function
的实例(包括Function
自身))
五、推荐链接
之前看到这篇博客里有详细的原型链示意图,有兴趣的朋友可以详细去看一下: