为何须要继承?javascript
当项目愈来愈大,代码愈来愈复杂,部分功能开始重复就得考虑代码复用,在代码复用的前提下把能抽象出来的属性和方法变成一个类,这个就是所谓的抽象类,若是须要在这个抽象类的基础上还须要进行功能拓展可是又要建立一个基于此类的抽象类就须要用到继承了。java
如下继承方法都是基于ES5实现,ES6有专门的继承语法糖了暂不作讨论app
常见的继承有哪几种?函数
顾名思义类式继承就是直接经过类的方法来继承,咱们都知道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
构造函数继承说的简单点就是借助父类的构造函数来实现继承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') // 报错
把组合继承放到第一第二种下面讲,就是由于组合就是原型和构造函数继承相组合来实现继承,弥补了以上两种继承的缺点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
基于已有对象建立新对象。若是上面三种继承方式属于一类,下面讲的几种,包括原型式继承就是另外一类继。原型是继承没有使用严格的构造函数,必须有一个对象能够做为另外一个对象的基础,将源对象传入建立封装对象的函数,再修改目标对象对象
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"\]
原型式继承的好处是基于已有对象建立新对象,同时还没必要所以建立自定义类型,可是因为用到了原型链因此子类在修改父类引用型数据时,数据会在全部子类共享继承
寄生继承和原型式继承相似,都是经过建立一个用于封装继承的方法来生成对象。可是这个方法里面能够增长一些新增的方法。这里的建立对象用到了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写法其实就是把传入的对象封装一遍实例化,再对这个实例化对象进行扩展,最后再把这个对象实例化获得目标对象。能够说是原型式继承的再一次封装。
寄生组合式继承是目前来讲最好的继承方式,先说组合式继承,他是比较好的继承方法,可是他会致使对象的原型链和对象属性上出现两套相同的属性,也就是父类构造函数会执行两次。寄生组合式的继承方法就是把子类对象上的属性去掉 只保留原型链上的属性。
寄生组合式继承要理解要写出来,最好先从写一个组合继承开始,再将寄生继承的概念融入
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()方法把多个父类的原型链合并为一个对象进行拷贝,就能够实现多重继承