JavaScript必知33个概念系列:原型继承和原型链
prototype inheritance、prototype chain
前言
JS中的继承是通过原型来实现的,这一部分内容看过很多次,但是一直没有总结,今天就来总结一下这一部分内容
4个问题
首先来看这四个判断,我们的内容会由这四个判断展开。
1 | Object instanceof Function//true |
必须明确的几个关键点
- 每个对象都可以访问一个
__proto__
属性,指向创建当前对象的构造函数的prototype
对象。 - 函数也是对象,可以称之为
function object
,即函数对象。 - 每个函数都有一个
prototype
属性,指向原型对象。 - 每个函数的
prototype
对象里都有一个constructor
属性指向函数本身。 - 原型链的尽头是
Object.prototype
,他没有__proto__
属性,实际上有这个属性,但是为null
一些例子
例子1
1 | let obj=new Object(); |
因为obj
是一个对象,所以它有一个__proto__
属性,并且它由Object
构造函数创建,Object
函数有一个prototype
属性,因此obj.__proto__
和Object.prototype
指向同一个对象。不妨自己动手打印一下,这两个对象是一样的。
例子2
1 | Object.prototype.constructor === Object//true |
因为每个函数的prototype
属性都有一个constructor
属性指向函数本身,因此Object.prototype.constructor
指向Object
函数。
例子3
1 | Object.__proto__ === Function.prototype//true |
因为函数也是对象(function object
),因此它有一个__proto__
属性,那这个属性指向哪呢?函数对象是由哪个构造函数创建的?很容易猜到,就是Function
对象。因此Object.__proto__
指向Function.prototype
。
例子4
1 | Function.prototype === Function.__proto__//true |
还是如此,函数也是对象。Function
函数有一个__proto__
属性,那它指向哪呢?因为Function
也是一个函数,创建函数的很容易想到是Function
,可能有点奇怪,不过就是如此。
例子5
1 | Function.prototype.constructor === Function//true |
每个函数的原型对象都会有一个constructor
属性指向函数本身,因此Function.prototype.constructor
就是指向Function
函数。
例子6
1 | Function.prototype.__proto__ == Object.prototype//true |
某个函数的原型对象是一个对象,那么他的__proto__
属性指向谁呢?因为Object
函数用于创建对象,因此Function.prototype.__proto__
就指向Object.prototype
。
instanceof怎么判断的以及4个问题的答案
就如下代码,instanceof
的判断条件是,如果在b.prototype
在a
的原型链中就返回true
,如果没有则false
。
1 | a instanceof b |
因此一开始的4个问题的答案就很容易解答了。
问题1
1 | Object instanceof Function//true |
因为Object.__proto__
指向Function.prototype
,即Function.prototype
在Object
的原型链上。
问题2
1 | Object instanceof Object//true |
因为Object.__proto__.__proto__
指向Object.prototype
,即Object.prototype
在Object
的原型链上。
问题3
1 | Function instanceof Function//true |
因为Function.__proto__
指向Function.prototype
,即Function.prototype
在Function
的原型链上。
问题4
1 | Function instanceof Object//true |
因为Function.__proto__.__proto__
指向Object.prototype
,即Object.prototype
在Function
的原型链上。
如何实现原型继承
总共分3步(这没有实现静态方法的继承):
super.call(this)
child.prototype=Object.create(super.prototype)
child.prototype.constructor=child
例子
1 | function Animal(name){ |
静态的继承
在ES6的class-extends
方式中,你可以指定静态static
的方法和属性,如下:
1 | class Animal{ |
因为Dog
函数本身没有这个方法,那么肯定在它的原型链上,问题是Dog的原型根据ES5的写法应该Dog.__proto__===Function.prototype
为true
,然而这个例子中Dog.__proto__===Animal
为true
,这说明上面ES5的继承方法缺少了静态继承,补全以上代码的话应该有4步:
super.call(this)
child.prototype=Object.create(super.prototype)
child.prototype.constructor=child
child.__proto__=Animal
1
2
3
4
5
6
7
8
9
10
11function Animal(name){
this.name=name;
}
function Cat(name,color){
Animal.call(this,name);//#1,构造父类属性
this.color=color;
}
Cat.prototype=Object.create(Animal.prototype);//#2,实现原型链继承
Cat.prototype.constructor=Cat;//#3,设置构造函数
Cat.__proto__=Animal//#4,实现静态继承
头条面经的几个问题
在看头条面经时看到的问题,来一一分析一下。
1 |
|
打印1
1 | console.log(Person.a)//Function |
首先Person
是一个函数对象,它本身没有a
这个属性,因此需要沿着原型链向上找。函数是由Function
创建的,因此Person.__proto__
指向Function.prototype
,而Function.prototype
有属性a
,值为“Function”因此结果为“Function”。
打印2
1 | console.log(child.a)//Object |
首先child
对象本身没有这个属性,因此需要沿着原型链向上找。child
由Person
创建,因此child.__proto__
指向Person
的原型,Person.prototype
没有这个属性,因此继续向上找,Person.prototype
是由Object
创建的,因此Person.prototype.__proto__
指向Object
的原型对象,而Object.prototype.a
是“Object”,因此结果是“Object”
打印3
1 | console.log(child.__proto__)//Person.prototype |
child
对象由Person
创建,因此child.__proto__
指向Person.prototype
,打印的就是Person.prototype
,不出意外的话里面有一个constructor
属性指向Person
,还有一个__proto__
属性指向Object.prototype
。
打印4
1 | console.log(child.__proto__.__proto__)//Object.prototype |
参考打印3,child.__proto__
指向Person.prototype
,因此就等价于打印Person.prototype.__proto__
,而Person.prototype
是由Object
创建的,因此Person.prototype.__proto__
指向Object.prototype
因此打印的就是Object.prototype
里面的内容。
打印5
1 | console.log(child.__proto__.__proto__.constructor)//Object |
参考打印4,直到child.__proto__.proto__
指向Object.prototype
,那它的constructor
属性就指向Object
函数本身。
打印6
1 | console.log(child.__proto__.__proto__.constructor.constructor)//Function |
参考打印5,知道child.__proto__.__proto__.constructor
指向的是Object
,那么Object.constructor
指向的谁呢?首先Object
本身没有constructor
属性,那么就沿着原型链向上找,而Object
是一个函数对象,即由Function
创建,因此Object.__proto__
指向Function.prototype
,而Function.prototype.constructor
是Function
本身,因此结果就是“Function”。
打印7
1 | console.log(child.__proto__.__proto__.constructor.constructor.constructor)//Function |
参考打印6,因为child.__proto__.__proto__.constructor.constructor
指向Function
,因此等价于Function.constructor
,而Function
函数对象本身没有这个属性,于是沿着原型链向上找,Function.__proto__
指向Function.prototype
,而Function.prototype.constructor
是Function
本身,因此结果也是“Function”
总结
- 每个对象都可以访问一个
__proto__
属性,指向创建当前对象的构造函数的prototype
对象。 - 函数也是对象,可以称之为
function object
,即函数对象。 - 每个函数都有一个
prototype
属性,指向原型对象。 - 每个函数的
prototype
对象里都有一个constructor
属性指向函数本身。 - 原型链的尽头是
Object.prototype
,他没有__proto__
属性,实际上有这个属性,但是为null