实际应用
我发现在学习某个东西之前,我先要知道它的作用是什么,才学得更深入。所以在描述知识点之前,先举一个例子,表明一下原型和原型链的作用:
// 构造函数
function Person(){}
Person.prototype.name = 'sugarMei';
// 创建实例
let person = new Person();
person.name = '王花花';
console.log(person.name); // 王花花
delete person.name;
console.log(person.name); // sugarMei
我们创建了一个构造函数叫做Person
,在它的prototype
上定义了一个属性name
;之后我们使用new Person()
创建了一个person
实例,在person
上定义了属性name
。
当我们打印person.name
时,第一次是显示王花花
,之后我们删除了person.name
,然后再次读取person.name
时,由于person
上没有name
属性了,这时候应该怎么办?
此时它会沿着原型链去找它的原型上是否存在这个属性,如果存在就会读取,即读取到了Person
上的name
值,打印了sugarMei
。
什么是原型
在学习之前我们先学习几个概念:
prototype
当person
中的name
属性不存在时,打印出来的person.name
的值就是Person.prototype.name
的值
function Person(){}
Person.prototype.name = 'sugarMei';
规定,每一个函数都有一个prototype
属性。Person.prototype
指向了一个对象,在这个对象上面你可以定义或者读取属性或者函数。prototype
称为原型。
当在person
上查找不到name
时,就会去找Person
的原型对象,即Person.prototype
,查找它上的name
属性。那么person
是通过什么去找Person.prototype
呢,答案是__proto__
。
__proto__
规定,除了null
外,每一个JS
对象都有一个__proto__
属性,这个属性会指向该对象的原型。
console.log(person.__proto__ === Person.prototype); // true
当在person
上找不到name
属性时,系统会隐形地通过__proto__
找到这个对象的原型Person.prototype
,从而找到它上面的name
值。
什么是原型链
在上面我们了解了prototype
和__proto__
,如果你看懂了上面的内容,那么就能理解原型链了。
当我们访问一个对象的属性的时候,如果这个属性在对象内部存在,就直接读取,如果不存在,就沿着__proto__
找到它的原型,查找这个属性,如果还是不存在,则继续查找原型的原型。
这就是原型链了。拿回原先的例子:
// 构造函数
function Person(){}
Person.prototype.name = 'sugarMei';
// 创建实例
let person = new Person();
person.name = '王花花';
console.log(person.name); // 王花花
delete person.name;
console.log(person.name); // sugarMei
- 第一次访问
name
属性时,由于person
上存在该属性,则直接读取,打印的值是王花花
; - 删除
person
上的name
属性; - 第二次访问
name
属性时,由于person
上不存在该属性,则系统隐性地通过__proto__
查找这个对象的原型,即Person.prototype
,它存在着name
属性,则打印出了sugarMei
。
原型链的顶层
前面我们说过Person.prototype
指向了一个对象,即可以说原型也是一个对象。又说道:除了null
外,每一个JS
对象都有一个__proto__
属性。那么Person.prototype
这个对象也拥有了__proto__
这个属性。
由于所有对象(除了null
)都是可以由new Object()
创建,故所有对象的原型链顶层就指向了Object
。即Person.prototype.__proto__ === Object.prototype
。
// person.__proto__ 指向了构造函数的prototype
console.log(person.__proto__ === Person.prototype); // true
// Person.prototype.__proto__指向了构造函数的prototype
console.log(Person.prototype.__proto__ === Object.prototype); // true
如果上面的例子修改为这样的:
// 构造函数
function Person(){}
// Person.prototype.name = 'sugarMei';
// 创建实例
let person = new Person();
person.name = '王花花';
console.log(person.name); // 王花花
delete person.name;
console.log(person.name); // undefined
那么访问的顺序应该是这样的:
- 第一次访问
name
属性时,由于person
上存在该属性,则直接读取,打印的值是王花花
; - 删除
person
上的name
属性; - 第二次访问
name
属性时,由于person
上不存在该属性,则系统隐性地通过__proto__
查找这个对象的原型,即Person.prototype
,它不存在着name
属性,系统隐性地通过__proto__
查找这个对象的原型,即使用Person.prototype.__proto__
; -
Person.prototype.__proto__
指向的是Object.prototype
,由于Object.prototype.name
不存在,然后查找Object.prototype.__proto__
,由于
console.log(Object.prototype.__proto__ === null);
即Object.prototype__proto__
没有原型,查找属性就到这里为止了,输出undefined
。
(这里蓝色的线条即为原型链)
constructor
在上面的图我们发现:
function Person(){};
console.log(Person.prototype.constructor === Person); // true
规定,每个原型都有一个constructor
属性指向关联的构造函数。
Object.getPrototypeOf()
其实我们现在很少推荐用__proto__
来获得原型,会使用Object.getPrototypeOf()
:
console.log(person.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(person) === Person.prototype); // true
实践
在学习JS的时候,我们会通过instanceof
来判断是不是一个引用类型:
let arr = new Array();
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
再结合我们学的知识:
let arr = new Array();
console.log(arr.__proto__ === String.prototype); // false
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); //true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
console.log(arr.__proto__.__proto__.__proto__ === null); // true
可以来手写一下instanceof
的实现方法:
function myInstanceof(left, right){
while(true){
if(left.__proto__ === right.prototype){
return true;
}else if(left.__proto__ === null){
return false;
}else {
left = left.__proto__;
}
}
}
let arr = new Array();
console.log(myInstanceof(arr, String)); // false
console.log(myInstanceof(arr, Array)); // true
console.log(myInstanceof(arr, Object)); // true
总结
- 每一个构造函数都有
prototype
属性,这个属性我们称为原型; - 每一个对象都有一个
__proto__
属性,这个属性指向了这个对象的构造函数的原型对象,即person.__proto__ === Person.prototype
; - 原型对象即对象,是对象就有
__proto__
属性,即存在Person.prototype.__proto__
; - 在访问一个对象的属性时,如果不存在就顺着原型链去它的原型对象上找,一直找到这个属性或者其没有原型对象为止;
- 一般不使用
__proto__
来访问原型,而使用Object.getPrototypeOf()
。