原型链
.
显式原型和隐式原型基本概念
所有的构造函数(对象)都有一个属性:
prototype: 显式原型(属性),默认指向它的原型对象。显示原型在定义函数时自动添加,一般可以进行人为操作。- 每个原型对象中有一个属性:constructor,默认指向这个函数对象
由函数对象创建的实例对象都有一个属性:
[[Prototype]](实际中是__proto__) : 隐式原型(属性),默认指向它构造函数对象的原型对象。隐式原型在创建对象时自动添加,它是内部指针,一般禁止对其进行直接操作。1
2
3
4
5
6
7
8
9
10
11
12
13function Person(){
}
Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function() {
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
console.log(person1.sayName === person2.sayName); //true
显式原型和隐形原型的关系:构造函数的显式原型的值和它实例对象的隐式原型的值相同,都指向构造函数的原型对象。
显式原型和隐式原型的生成过程
1 | |
以上过程的内存解析:
显示原型和隐式原型
- 每创建一个函数,该函数就有一个prototype,即显式原型属性(简称显式原型),指向一个空的Object对象(原型对象)
- 每给函数创建一个实例对象,该对象就有一个__proto__(书写上写作[[prototype]]), 即隐式原型属性(简称隐式原型),其值为其对应构造函数的显式原型的值。简而言之,隐式原型和显式原型中保存的地址值是相同的,都指向原型对象。ES5之前的版本,一般不宜对__proto__做直接操作。
- 对象的隐式原型的值为其对应构造函数显式原型的值
- 对于函数Function(),它有一个prototype,该函数自身的实例对象Function有一个__proto__。它们都指向原型对象。简而言之,Function函数的显式原型和该函数自己创建的隐式原型的值相同(其他函数并不存在这一点特征)。
- 函数所有实例的隐式原型__proto__的值都等于该函数显式原型prototype的值,并等于原型对象的地址。
1 | |
原型链
原型链(隐式原型属性链):访问一个对象属性时,先在自身属性中查找,可以找到就返回该属性值;若没有找到,则沿着__proto__这条链依次向上查找;若果在Object原型对象(原型链的尽头)仍没有找到该属性,则返回undefined。
但是JavaScript设定:Object的原型对象指向null
许多面向对象语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在 ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链(又称隐式原型链)来实现的。
主要思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
依据:每一个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针(constructor),而函数的实例对象都存在一个指向原型对象的指针(__proto__)。假如实例所指向的原型对象本身又是另一个构造函数的实例对象(称另一个类的实例),那么此时原型对象(记为原型对象1)中就包含一个指向另一个原型对象(记为原型对象2)的指针,相应的,另一个原型对象包含一个指向另一个构造函数的指针。进一步可知,原实例对象与原型对象2的关系是:原实例对象的隐式原型的隐式原型指向原型对象2。事实上,原型对象2是原型对象1的父类型,原型对象1是原实例对象的父类型。
原型链的作用:访问对象属性
- 访问一个对象的属性时,
① 先在实例对象自身属性中查找,看是否有该属性,若找到则直接返回该属性
② 如果没有找到, 再沿着__proto__这条链(即原型链)向上查找, 直到找到则返回该属性
③ 若始终没有找到,这种沿着原型链向上游查找也不是无限制的。JS中,一切对象都是Object()的实例对象,所以,它的原型对象将作为查找的终点。如果最终在Object()的原型对象Object.prototype中仍没有找到该属性, 则返回undefined(这个对象再没有实际指向了,它的隐式原型Object.prototype.__proto__值为null,是个空指针)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27//创建父类
function Person(name, age) {
this.name = name;
this.age = age;
}
//创建子类
function Student(name, age, sex) {
Person.call(this, name, age); // 提取父类的属性
this.sex = sex; //子类自己添加sex属性
}
Student.prototype = new Person(); // 真正实现继承
Student.prototype.constructor = Student; //修正constructor属性
//为Student创建一个实例对象girl
var girl = new Student("silybily", 26, "female");
//检验:
console.log(girl);
girl.__proto__ === Student.prototype; //true
girl.__proto__.__proto__ === Person.prototype; //true
girl.__proto__.__proto__.__proto__ === Object.prototype; //true
girl.__proto__.__proto__.__proto__.__proto__ === null; //true
console.log(girl.name); //silybily
console.log(girl.hobby); //undefined
有一点需要特别注意的是:构造函数本身,首先是一个对象,理解了这一点,上面的过成就不难理解了
探索instanceof
作用:判断函数的显式原型是否在另一个对象的原型链上
语法:A instanceof B 判断B函数的显式原型是否在A对象的原型链上。如果存在则返回true,否则返回false
1 | |
由function Foo() { },构造函数Foo()的显式原型指向一个空的Object原型对象{ }
由var f1 = new Foo();,声明了一个变量f1指向函数Foo()的实例对象
由以上两步可知,Foo.prototype === f1.__proto__ === { }因此Foo()的显式原型在对象f1的原型链上
Object是任何实例的构造函数,显然在f1的原型链上
1 | |
这个是怎么一回事呢?仍要回头看instanceof得定义:A instanceof B判断A的原型链上是否存在B.prototype。而在JavaScript中,Object和Function之间的关系可以用一张图来说明:
从上图中抽离Function和Object的关系,即:

根据该图①,再来看一下代码就一目了然了:
1 | |
如果看完以上,你还觉得上面的关系看晕了的话,只需要记住下面两个最重要的关系,其他关系就可以推导出来了:
1、所有的构造器的constructor都指向Function
2、Function的prototype指向一个特殊匿名函数,而这个特殊匿名函数的__proto__指向Object.prototype
总结
- 显式原型与隐式原型的关系
- 函数的prototype: 定义函数时被自动赋值, 原型对象的值默认为{}
- 实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
- 原型对象即为当前实例对象的父对象
- 原型链
- 所有的实例对象都有__proto__属性, 它指向的就是原型对象
- 这样通过__proto__属性就形成了一个链的结构—->原型链
- 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作
- 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
版权声明:本文作者为「Andy8421」.本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!