关于构造函数、原型、原型链、多种方式继承

构造函数与实例对象

又是这个经典的问题,嗯,我先来写个构造函数,而后实例化一个对象看看。segmentfault

function Person(name) {
  this.name = name
}
Person.prototype.eat = () => {console.log('eat')}
Person.prototype.play = () => {console.log('play')}
let Han = new Person('Han')

经过一系列打印发现了这样的关系:
图片描述性能优化

原型链 -- 原型(prototype)和隐式原型(__proto__)

能够看出实例对象没有prototype(也就是原型),只有构造器才拥有原型。而全部的js对象都拥有__proto__(也就是隐式原型),这个隐式原型所指向的就是创造这个对象的构造器的原型。如实例Han的隐式原型指向了其构造函数(Person)的原型;Person的隐式原型指向了Function的原型;而原型自身也有隐式原型,指向了Object的原型。函数

有点绕口,其实就是经过隐式原型能够向上找到是谁构造了本身,而且若是本身没有相应的属性或者方法,能够沿着这条原型链向上找到最近的一个属性或方法来调用。如Han.eat(),其实是调用了Han.__proto__.eat(),把构造器Person的原型的eat方法给拿来用了;再如Han.hasOwnProperty('name'),其实是调用了Han.__proto__.__proto__.hasOwnProperty('name'),由于Han本身没hasOwnProperty这方法,就经过隐式原型向上找到了Person的原型,发现也没这方法,就只能再沿着Person的原型的隐式原型向上找到了Object的原型,嗯而后发现有这方法就拿来调用了。性能

构造器constructor

全部构造函数都有本身的原型(prototype),而原型必定有constructor这么个属性,指向构造函数自己。也就是告诉你们这个原型是属于本构造函数的。优化

Function & Object

能够看出Person这个构造函数是由Function建立出来的,而咱们看下Function的隐式原型,居然是指向了Function的原型,也就是Function也是由Function建立出来的。很绕是否是,咱们先无论,继续溯源下去,再看下Function的原型的隐式原型,指向的是Object的原型,继续往上找Object的原型的隐式原型,嗯终于结束了找到的是null,也就是Object的原型是原型链上的最后一个元素了。this

接下来看下Object,Object是由Function建立出来的,而Function的隐式原型的隐式原型是Object的原型也就是Function经过原型链能够向上找到Object的原型,二者看起来是你生我我生你的关系,这里也就引用比较好懂的文章来解释下: 从Object和Function说说JS的原型链spa

Object
JavaScript中的全部对象都来自Object;全部对象从Object.prototype继承方法和属性,尽管它们可能被覆盖。例如,其余构造函数的原型将覆盖constructor属性并提供本身的toString()方法。Object原型对象的更改将传播到全部对象,除非受到这些更改的属性和方法将沿原型链进一步覆盖。

Function
Function 构造函数 建立一个新的Function对象。 在 JavaScript 中, 每一个函数实际上都是一个Function对象。prototype

---- 来自mozilla3d

接下来讲下构造函数实例化对象到底作了些啥,其实看也能看出来了。code

let Jan = {}
Person.call(Jan, 'Jan')
Jan.__proto__ = Person.prototype

一、建立一个空对象。
二、将构造函数的执行对象this赋给这个空对象而且执行。
三、把对象的隐式原型指向构造函数的原型。
四、返回这个对象

是的就是这样,next page!

继承

原型链继承

function Person(name) {
  this.name = name
  this.skills = ['eat', 'sleep']
}
Person.prototype.say = ()=> {console.log('hi')}

function Boss() {}
Boss.prototype = new Person()
let Han = new Boss()

原理就是这样👇
图片描述
子构造函数的原型指向了父构造函数的实例对象,所以子构造函数的实例对象能够经过原型链找到父构造函数的原型方法和类属性。

优势:
全部实例对象均可以共享父构造函数的原型方法。

缺点:
一、父构造函数的引用属性也被共享了,至关于全部的实例对象只要对自身的skills属性进行修改都会引起共振,由于其实修改的是原型链上的skills属性。固然对skills从新赋值能够摆脱这一枷锁,至关于自身新建了skills属性来覆盖了原型链上的。
二、实例化时没法对父构造函数传参。
三、子构造函数原型中的constructor再也不是子类自身,而是经过原型链找到了父类的constructor。

构造函数继承

function Person(name) {
  this.name = name
  this.skills = ['eat', 'sleep']
}
Person.prototype.say = ()=> {console.log('hi')}

function Boss(name) {
  Person.call(this, name)
}
let Han = new Boss('Han')

原理就是父构造函数把执行对象赋给子构造函数的实例对象后执行自身。

优势:
一、实例化时能够对父构造函数传参。
二、父类的引用属性不会被共享。
三、子构造函数原型中的constructor仍是自身,原型没有被修改。

缺点:
每次实例化都执行了一次父构造函数,子类不能继承父类的原型,若是把父类原型上的方法写在父类的构造函数里,虽然子类实例对象能够调用父类的方法,但父类的方法是单独加在每一个实例对象上,会形成性能的浪费。

组合继承

结合了原型链继承和构造函数继承两种方法。

function Person(name) {
  this.name = name
  this.skills = ['eat', 'sleep']
}
Person.prototype.say = ()=> {console.log('hi')}

function Boss(name, age) {
  Person.call(this, name)
  this.age = age
}

Boss.prototype = new Person()
Boss.prototype.constructor = Boss
Boss.prototype.sleep = ()=> {console.log('sleep')}

let Han = new Boss('Han', 18)

看起来是完美解决了一切。但就是👇

clipboard.png

实例化的对象其实是用构造函数继承的方法往本身身上加属性从而覆盖原型链上的相应属性的,既然如此,为何不直接那父构造器的原型加到子构造器的原型上呢?这样就不会出现那多余的父类实例化对象出来的属性了。

function Person(name) {
  this.name = name
  this.skills = ['eat', 'sleep']
}
Person.prototype.say = ()=> {console.log('hi')}

function Boss(name, age) {
  Person.call(this, name)
  this.age = age
}

Boss.prototype = Person.prototype  //modified
Boss.prototype.constructor = Boss
Boss.prototype.sleep = ()=> {console.log('sleep')}

let Han = new Boss('Han', 18)

clipboard.png

看起来非常完美,反正效果是达到了,性能优化也是最佳。但问题就是这样一点继承关系都看不出来啊,父类和子类的原型彻底融合在一块了,一点都不严谨。

因此最优的继承方式应该是。。。

寄生组合继承

function Person(name) {
  this.name = name
  this.skills = ['eat', 'sleep']
}
Person.prototype.say = ()=> {console.log('hi')}

function Boss(name, age) {
  Person.call(this, name)
  this.age = age
}
Boss.prototype = Object.create(Person.prototype)
Boss.prototype.sleep = ()=> {console.log('sleep')}
Boss.prototype.constructor = Boss
let Han = new Boss('Han', 18)

先看图👇
图片描述
其实跟组合继承有点像,构造函数继承部分和组合继承的同样就不说了。原型链那块和原型链继承有所不一样的是原型链继承是直接拿了父类的实例对象来做为子类的原型,而这里是用以父类的原型为原型的构造函数实例化出来的对象做为子类的原型(Object.create作的事情),完美避开了没必要要的父类构造函数里的东西。

Object.create()方法建立一个新对象,使用现有的对象来提供新建立的对象的__proto__。

至关于这样👇

function create(proto) {
  function F() {}
  F.prototype = proto
  return new F()
}

据说ES6的class extend也是这么作的,更多的继承细节能够看看这篇文章,本继承章节也参考了的👇
一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends