了解Javascript的原型链

7/27/2021 原型链

# 了解Javascript的原型链

根据概念可以知道,每个构造函数都有一个原型对象,原型对象有一个属性constructor指回构造函数,而实例有一个内部指针指向原型。那么,如果原型是另一个构造函数的实例呢?

  1. # 构造函数、实例、对象原型

# 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);
    }
}
1
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
1
2
3
4
5

可以看到,person1和person2是使用构造函数Person所创建的不同实例,这两个对象都有一个共同的属性constructor指向Person;

console.log(person1.sayName === person2.sayName ) //false
1

可以看到,使用同一构造函数创建的实例,其定义的方法会在每个实例上都创建一遍。

# 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
1
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方法,因为两者的原型对象都指向同一个
1
2
3
4
5
6
7
8
9
10
11

具体可以看《Javascript高级程序设计》中画的关系图,基本比较清晰了

关系图

  1. # 原型链

根据概念可以知道,每个构造函数都有一个原型对象,原型对象有一个属性constructor指回构造函数,而实例有一个内部指针指向原型。那么,如果原型是另一个构造函数的实例呢?

其实在上一节介绍原型对象时说过,所有原型对象会自动获得一个名为constructor的属性,指回与之关联的构造函数。但其实,创建一个自定义的函数,原型对象除了获得constructor属性外,还会有许多方法,例如toString方法、valueOf方法等,其实这些方法都是继承自Object的,这就涉及到原型链。也有个内部指针指向另一原型,相应的另一原型的constructor属性也指向另一个构造函数,这样在实例和原型之间就形成了一条原型链。

# 2.1 原型链的搜索机制

  • 在读取实例上的属性时,会先搜索实例上的属性;
  • 如果实例上没有找到,继续搜索实例原型上的属性,如果实例上找到了,停止搜索;
  • 搜索如果一直没找到,会一直搜素到原型链的末端,如果没有该属性,返回false;

# 2.2 原型链的末端

  • 所有的引用类型都继承自Object,任何函数的默认原型都是一个Object的实例,这样就是为啥所有自定义函数都能够使用包括toString方法、valueOf方法这些所有默认方法的原因;
  • 正常的原型链都会终止于Object的原型对象;
  • Object的原型对象的原型对象是null;