爲何須要繼承?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()方法把多個父類的原型鏈合併爲一個對象進行拷貝,就能夠實現多重繼承