JavaScript六大继承方式

解析几种继承方式

为何须要继承?javascript

当项目愈来愈大,代码愈来愈复杂,部分功能开始重复就得考虑代码复用,在代码复用的前提下把能抽象出来的属性和方法变成一个类,这个就是所谓的抽象类,若是须要在这个抽象类的基础上还须要进行功能拓展可是又要建立一个基于此类的抽象类就须要用到继承了。java

如下继承方法都是基于ES5实现,ES6有专门的继承语法糖了暂不作讨论app

常见的继承有哪几种?函数

  1. 类式继承
  2. 构造函数继承
  3. 组合继承
  4. ...下次补上

1.类式继承(原型链继承)

顾名思义类式继承就是直接经过类的方法来继承,咱们都知道JS是一门基于原型的语言,他是没有具体类的概念的,可是咱们能够经过prototype来实现关于类的种种功能。因此简而言之类式继承就是把子类的prototype链挂载到实例化的父类对象上。this

function  Father()  {

this.name  =  'dad'

this.clothColor  =  ['red',  'blue',  'green']

}

  

Father.prototype.addColor =  function(color)  {

this.clothColor.push(color)

}

  

Father.prototype.sayHi  =  function()  {

console.log('Hi')

}

  

function  Son()  {

this.name  =  'son'

this.age  =  10
 this.clothColor = ['white']

}

  

Son.prototype  =  new  Father()

  

let  fathers  =  new  Father()

let  sons1  =  new  Son()

let  sons2  =  new  Son()

  

sons1.addColor('yellow')

  

console.log(fathers.clothColor)  //\["red", "blue", "green"\]

console.log(sons1.clothColor)  // \["red", "blue", "green", "yellow"\]

console.log(sons2.clothColor)  // \["red", "blue", "green", "yellow"\]

console.log(sons1.sayHi())  // Hi

类式继承的优势是实现了代码的复用,每次实例化都不用再去执行类代码,全部子类获得了同样的功能,可是缺点也很明显,一个子类改变了父类引用类型数据,全部子类都会改变prototype

2.构造函数继承

构造函数继承说的简单点就是借助父类的构造函数来实现继承code

function  Father()  {

this.name  =  'dad'

this.clothColor  =  ['red',  'blue',  'green']

}

  

Father.prototype.addColor  =  function(color)  {

this.clothColor.push(color)

}

  

Father.prototype.sayHi  =  function()  {

console.log('Hi')

}

  

function  Son()  {

Father.call(this)

// Father.prototype.constructor = Father

  

this.name  =  'son'

this.age  =  10

}

  
  

let  fathers  =  new  Father()

let  sons1  =  new  Son()

let  sons2  =  new  Son()

  

sons1.addColor('yellow')  // 报错
  1. 构造函数继承和类式继承很类似,只不过是把Son.prototype = new Father()改为了Father.call(this),可是其实没有涉及到原型的继承,只是经过call、apply改变了父类的this指向,把this绑定到了Son类上,因此Son类没法获得父类的方法。
  2. 使用call、apply会致使每次实例化都会去执行父类,不符合代码复用思想,会形成大量内存堆积。
  3. 在每一个子类实例中,每一个方法都做为了实例本身的方法,当需求改变,要改动其中的一个方法时,以前全部的实例,他们的该方法都不能及时做出更新。只有后面的实例才能访问到新方法。

3. 组合继承

把组合继承放到第一第二种下面讲,就是由于组合就是原型和构造函数继承相组合来实现继承,弥补了以上两种继承的缺点orm

function  Father()  {

this.name  =  'dad'

this.clothColor  =  ['red',  'blue',  'green']

console.log('调用次数')  //4

}

  

Father.prototype.addColor  =  function(color)  {

this.clothColor.push(color)

}

  

Father.prototype.sayHi  =  function()  {

console.log('Hi')

}

  

function  Son()  {

Father.call(this)

this.name  =  'son'

this.age  =  10

}

  

Son.prototype  =  new  Father()

  

let  fathers  =  new  Father()

let  sons1  =  new  Son()

let  sons2 =  new  Son()

  

sons1.addColor('yellow')

  

console.log(fathers.clothColor)  //\["red", "blue", "green"]

console.log(sons1.clothColor)  // \["red", "blue", "green", "yellow"\]

console.log(sons2.clothColor)  // \["red", "blue", "green"\]

console.log(sons1.sayHi())  // Hi
  1. 组合继承结合了以上两种继承的优缺点,比较经常使用
  2. 可是组合继承会使子类拥有两套属性,一个是经过子类点直接访问,一个是在子类的原型链上,效率较低
  3. 每次子类实例化都会去调用父类的问题依然没获得解决,效率较低

4. 原型式继承

基于已有对象建立新对象。若是上面三种继承方式属于一类,下面讲的几种,包括原型式继承就是另外一类继。原型是继承没有使用严格的构造函数,必须有一个对象能够做为另外一个对象的基础,将源对象传入建立封装对象的函数,再修改目标对象对象

function  inheritObject(obj)  {

function  Func()  {}

Func.prototype  =  obj

return  new  Func()

}

  

let  man  =  {

name:  'Norman',

clothColor:  ['red',  'blue']

}

  

let  newMan  =  inheritObject(man)

let  secondMan  =  inheritObject(man)

  

newMan.clothColor.push('green')

  

console.log(newMan.clothColor)  //\["red", "blue", "green"\]

console.log(secondMan.clothColor)//\["red", "blue", "green"\]

原型式继承的好处是基于已有对象建立新对象,同时还没必要所以建立自定义类型,可是因为用到了原型链因此子类在修改父类引用型数据时,数据会在全部子类共享继承

5.寄生继承

寄生继承和原型式继承相似,都是经过建立一个用于封装继承的方法来生成对象。可是这个方法里面能够增长一些新增的方法。这里的建立对象用到了ES5的Object.create()

function  inheritObject(obj)  {

let  clone =  Object.create(obj)

clone.getName  =  function()  {  // 增长对象属性

return  'hhhh'

}

return  clone

}

  

let  man  =  {

name:  'Norman',

clothColor:  ['red',  'blue']

}

  

let  mans_1  =  new  inheritObject(man)

  

console.log(mans_1.name)

console.log(mans_1.getName())

缺点是每次建立一个新的对象都回去调用inheritObject方法不利于复用。

function  inheritObject(obj)  {

function  Func()  {}

Func.prototype  =  obj

return  new  Func()

}

// 等价于Object.create()

按照非ES5写法其实就是把传入的对象封装一遍实例化,再对这个实例化对象进行扩展,最后再把这个对象实例化获得目标对象。能够说是原型式继承的再一次封装。

6.寄生组合式继承

寄生组合式继承是目前来讲最好的继承方式,先说组合式继承,他是比较好的继承方法,可是他会致使对象的原型链和对象属性上出现两套相同的属性,也就是父类构造函数会执行两次。寄生组合式的继承方法就是把子类对象上的属性去掉 只保留原型链上的属性。

寄生组合式继承要理解要写出来,最好先从写一个组合继承开始,再将寄生继承的概念融入

function  Father()  {

this.name  =  'dad'

this.clothColor  =  ['red',  'blue']

}

  

Father.prototype.getWords  =  function()  {

return  'hahahah'

}

  

function  Mother()  {

this.baby  =  1

}

  

Mother.prototype.getBaby  =  function()  {

return  'baby'

}

  

function  Son()  {

Father.call(this)

Mother.call(this)  //多重继承 忘记写拿不到baby属性

this.age  =  15

}

  

function  inheritObject(sonClass, FatherClass, MotherClass)  {

let  clone

if  (MotherClass)  {

let  minin  =  Object.assign(FatherClass.prototype, MotherClass.prototype)

clone  =  Object.create(minin)  //多重继承

}  else  {

clone  =  Object.create(FatherClass.prototype)  //基于父原型建立新对象 这样操做会丢失构造函数默认的constructor属性

}

clone.constructor  =  sonClass  // 新对象的构造器和子类绑定

sonClass.prototype  =  clone  // 把新对象赋值给继承目标子类的原型链

}

  

inheritObject(Son,  Father,  Mother)

  

let  son  \=  new  Son()

  

console.log(son.getWords())

console.log(son.getBaby())

inheritObject函数接收两个参数:子类型构造函数和超类型构造函数。

建立父类型原型的副本。为建立的副本添加constructor属性,弥补因重写原型而失去的默认的constructor属性。将新建立的对象(即副本)赋值给子类型的原型。这种方法只调用了一次父类构造函数,instanceof 和isPrototypeOf()也能正常使用。

经过对inheritObject的改动能够传入多个父类参数,Son构造函数中添加其余父类的this指向,再经过Object.assign()方法把多个父类的原型链合并为一个对象进行拷贝,就能够实现多重继承

相关文章
相关标签/搜索