JavaScript必知33个概念系列:原型继承和原型链

prototype inheritance、prototype chain


前言

JS中的继承是通过原型来实现的,这一部分内容看过很多次,但是一直没有总结,今天就来总结一下这一部分内容

4个问题

首先来看这四个判断,我们的内容会由这四个判断展开。

1
2
3
4
Object instanceof Function//true
Object instanceof Object//true
Function instanceof Object//true
Function instanceof Function//true

必须明确的几个关键点

  • 每个对象都可以访问一个__proto__属性,指向创建当前对象的构造函数的prototype对象。
  • 函数也是对象,可以称之为function object,即函数对象。
  • 每个函数都有一个prototype属性,指向原型对象。
  • 每个函数的prototype对象里都有一个constructor属性指向函数本身。
  • 原型链的尽头是Object.prototype,他没有__proto__属性,实际上有这个属性,但是为null

一些例子

例子1

1
2
3
4
5
let obj=new Object();
//or
let o={};

obj.__proto__===Object.prototype//true

因为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.prototypea的原型链中就返回true,如果没有则false

1
a instanceof b

因此一开始的4个问题的答案就很容易解答了。

问题1

1
Object instanceof Function//true

因为Object.__proto__指向Function.prototype,即Function.prototypeObject的原型链上。

问题2

1
Object instanceof Object//true

因为Object.__proto__.__proto__指向Object.prototype,即Object.prototypeObject的原型链上。

问题3

1
Function instanceof Function//true

因为Function.__proto__指向Function.prototype,即Function.prototypeFunction的原型链上。

问题4

1
Function instanceof Object//true

因为Function.__proto__.__proto__指向Object.prototype,即Object.prototypeFunction的原型链上。

如何实现原型继承

总共分3步(这没有实现静态方法的继承):

  1. super.call(this)
  2. child.prototype=Object.create(super.prototype)
  3. child.prototype.constructor=child

例子

1
2
3
4
5
6
7
8
9
10
function 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,设置构造函数

静态的继承

在ES6的class-extends方式中,你可以指定静态static的方法和属性,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal{
constructor(){

}
static staticMethod(){
console.log("static method invoked!");
}
}

class Dog extends Animal{
constructor(){
super();
}
}
Dog.staticMethod();//static method invoked!

因为Dog函数本身没有这个方法,那么肯定在它的原型链上,问题是Dog的原型根据ES5的写法应该Dog.__proto__===Function.prototypetrue,然而这个例子中Dog.__proto__===Animaltrue,这说明上面ES5的继承方法缺少了静态继承,补全以上代码的话应该有4步

  1. super.call(this)
  2. child.prototype=Object.create(super.prototype)
  3. child.prototype.constructor=child
  4. child.__proto__=Animal
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function 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
2
3
4
5
6
7
8
9
10
11
12
13

Object.prototype.a='Object'
Function.prototype.a = 'Function'
function Person(){}
var child = new Person()

console.log(Person.a)
console.log(child.a)
console.log(child.__proto__)
console.log(child.__proto__.__proto__)
console.log(child.__proto__.__proto__.constructor)
console.log(child.__proto__.__proto__.constructor.constructor)
console.log(child.__proto__.__proto__.constructor.constructor.constructor)

打印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对象本身没有这个属性,因此需要沿着原型链向上找。childPerson创建,因此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

参考打印3child.__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.constructorFunction本身,因此结果就是“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.constructorFunction本身,因此结果也是“Function”

总结

  • 每个对象都可以访问一个__proto__属性,指向创建当前对象的构造函数的prototype对象。
  • 函数也是对象,可以称之为function object,即函数对象。
  • 每个函数都有一个prototype属性,指向原型对象。
  • 每个函数的prototype对象里都有一个constructor属性指向函数本身。
  • 原型链的尽头是Object.prototype,他没有__proto__属性,实际上有这个属性,但是为null

Reference

  • Prototype in Javascript
  • Prototypes in JavaScript
  • 北京头条前端面经
  • Prototype in JavaScript: it’s quirky, but here’s how it works