了解Javascript的原型链
# 了解Javascript的原型链
根据概念可以知道,每个构造函数都有一个原型对象,原型对象有一个属性constructor指回构造函数,而实例有一个内部指针指向原型。那么,如果原型是另一个构造函数的实例呢?
# 构造函数、实例、对象原型
# 1.1 构造函数
Javascript中构造函数是用于创建特定的类型对象的。既有像Object和Array这种的原生构造函数,也可以自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。如下:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job =job;
this.sayName = function(){
console.log(this.name);
}
}
2
3
4
5
6
7
8
- 构造函数也是函数 只是调用方式不同,使用new调用的是构造函数,不使用new调用的时候就是普通函数;
# 1.2 实例
let person1 = new Person('will',29,'Engineer')
let person2 = new Person('Mona',28,'Teacher')
console.log(person1.constructor === Person ) //true
console.log(person2.constructor === Person ) //true
2
3
4
5
可以看到,person1和person2是使用构造函数Person所创建的不同实例,这两个对象都有一个共同的属性constructor指向Person;
console.log(person1.sayName === person2.sayName ) //false
可以看到,使用同一构造函数创建的实例,其定义的方法会在每个实例上都创建一遍。
# 1.3 原型对象
根据Javascript的定义,每一个函数都有一个prototype属性,这个属性是一个对象,包含由特定引用类型的实例共享的属性和方法,这个对象就是通过调用构造函数创建的对象的原型。要记住,准确的描述来说,这个原型对象是实例的原型,而不是构造函数的原型。
console.log(String.prototype) //String {"", constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}
console.log(Array.prototype) //[constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]
console.log(Number.prototype) //Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
console.log(Object.prototype) //{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
// 可以看到,内置的一些构造函数都有其内置的属性和方法
console.log(Person.prototype) //{constructor: ƒ}
// 自定义的构造函数也有自己的prototype属性,里面的constructor属性指向构造函数Person
2
3
4
5
6
7
# 1.4 三者之间的关系
无论何时,只要创建一个函数,就会自动为这个函数创建一个prototype属性,指向原型对象
所以,有 构造函数.prototype
——————> 原型对象
而所有原型对象也会自动获得一个名为constructor的属性,指回与之关联的构造函数
所以,又有构造函数.prototype.constructor
——————> 构造函数
只要根据构造函数创建了一个新实例,这个实例的内部[[prototype]]指针就会被赋值为构造函数的原型对象。Firefox、Safari和Chrome会在每个对象上暴露__proto__属性,也可以通过这个属性访问对象的原型对象。
所以实例.__proto__
——————> 原型对象
综上:
- 实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有
所以,利用实例与构造函数原型之间的关系,可以让不同实例共享一个方法:
let Person = function(){};
Person.prototype.name = 'will';
Person.prototype.age = '29';
Person.prototype.job = 'engineer';
Person.prototype.sayName = function(){
console.log(this.name)
}
let person1 = new Person();
let person2 = new Person();
console.log(person1.sayName === person2.sayName ) //true
// 这次person1和person2共享一个sayName方法,因为两者的原型对象都指向同一个
2
3
4
5
6
7
8
9
10
11
具体可以看《Javascript高级程序设计》中画的关系图,基本比较清晰了
# 原型链
根据概念可以知道,每个构造函数都有一个原型对象,原型对象有一个属性constructor指回构造函数,而实例有一个内部指针指向原型。那么,如果原型是另一个构造函数的实例呢?
其实在上一节介绍原型对象时说过,所有原型对象会自动获得一个名为constructor的属性,指回与之关联的构造函数。但其实,创建一个自定义的函数,原型对象除了获得constructor属性外,还会有许多方法,例如toString方法、valueOf方法等,其实这些方法都是继承自Object的,这就涉及到原型链。也有个内部指针指向另一原型,相应的另一原型的constructor属性也指向另一个构造函数,这样在实例和原型之间就形成了一条原型链。
# 2.1 原型链的搜索机制
- 在读取实例上的属性时,会先搜索实例上的属性;
- 如果实例上没有找到,继续搜索实例原型上的属性,如果实例上找到了,停止搜索;
- 搜索如果一直没找到,会一直搜素到原型链的末端,如果没有该属性,返回false;
# 2.2 原型链的末端
- 所有的引用类型都继承自Object,任何函数的默认原型都是一个Object的实例,这样就是为啥所有自定义函数都能够使用包括toString方法、valueOf方法这些所有默认方法的原因;
- 正常的原型链都会终止于Object的原型对象;
- Object的原型对象的原型对象是null;