1. 程式人生 > >js中實現繼承的方法

js中實現繼承的方法

[TOC] ## 借用建構函式 這種技術的基本思想很簡單,就是在子型別建構函式的內部呼叫超型別的建構函式。另外,函式只不過是在特定環境中執行程式碼的物件,因此通過使用apply()和call()方法也可以在新建立的物件上執行建構函式。 ``` js function Box(name){ this.name = name } Box.prototype.age = 18 function Desk(name){ Box.call(this, name) // 物件冒充,物件冒充只能繼承構造裡的資訊 } var desk = new Desk('ccc') console.log(desk.name) // --> ccc console.log(desk.age) // --> undefined ``` 從中可以看到,繼承來的只有例項屬性,而原型上的屬性是訪問不到的。這種模式解決了兩個問題,就是可以傳參,可以繼承,但是沒有原型,就沒有辦法複用。 ## 組合繼承 ``` js function Box(name){ this.name = name } Box.prototype.run = function (){ console.log(this.name + '正在執行...') } function Desk(name){ Box.call(this, name) // 物件冒充 } Desk.prototype = new Box() // 原型鏈 var desk = new Desk('ccc') console.log(desk.name) // --> ccc desk.run() // --> ccc正在執行... ``` 這種繼承方式的思路是:用使用原型鏈的方式來實現對原型屬性和方法的繼承,而通過借用建構函式來實現對例項屬性的繼承。 ## 原型式繼承 原型式繼承:是藉助原型可以基於已有的物件建立新物件,同時還不必因此建立自定義型別。講到這裡必須得提到一個人,道格拉斯·克羅克福德在2006年寫的一篇文章《Prototype inheritance in Javascript》(Javascript中的原型式繼承)中給出了一個方法: ``` js function object(o) { //傳遞一個字面量函式 function F(){} //建立一個建構函式 F.prototype = o; //把字面量函式賦值給建構函式的原型 return new F() //最終返回出例項化的建構函式 } ``` 看如下的例子: ``` js function obj(o) { function F (){} F.prototype = o; return new F() } var box = { name: 'ccc', age: 18, family: ['哥哥','姐姐'] } var box1 = obj(box) console.log(box1.name) // --> ccc box1.family.push('妹妹') console.log(box1.family) // --> ["哥哥", "姐姐", "妹妹"] var box2 = obj(box) console.log(box2.family) // --> ["哥哥", "姐姐", "妹妹"] ``` 因為上述的程式碼的實現邏輯跟原型鏈繼承很類似,所以裡面的引用陣列,即family屬性被共享了。 ## 寄生式繼承 ``` js function obj(o) { function F (){} F.prototype = o; return new F() } function create(o){ var clone = obj(o) // 通過呼叫函式建立一個新物件 clone.sayName = function(){ // 以某種方式來增強這個物件 console.log('hi') } return clone // 返回這個物件 } var person = { name: 'ccc', friends: ['aa','bb'] } var anotherPerson = create(person) anotherPerson.sayName() // --> hi ``` 這個例子中的程式碼基於person返回一個新物件————anotherPerson。新物件不僅具有person的所有屬性和方法,而且還有自己的sayHi()方法。在主要考慮物件而不是自定義型別和建構函式的情況下,寄生式繼承也是一種有用的模式。使用寄生式繼承來為物件新增函式,會由於不能做到函式複用而降低效率,這一點與建構函式模式類似。 ## 寄生組合式繼承 前面說過,組合繼承是Javascript最常用的繼承模式,不過,它也有自己的不足。組合繼承最大的問題就是無論什麼情況下,都會呼叫過兩次超型別建構函式:一次是在建立子型別原型的時候,另一次是在子型別建構函式內部。沒錯,子型別最終會包含超型別物件的全部例項屬性,但我們不得不在呼叫子型別建構函式時重寫這些屬性,再來看一下下面的例子: ``` js function SuperType(name){ this.name = name; this.colors = ['red','black'] } SuperType.prototype.sayName = function (){ console.log(this.name) } function SubType(name, age){ SuperType.call(this, name) // 第二次呼叫SuperType this.age = age } SubType.prototype = new SuperType() // 第一次呼叫SuperType SubType.prototype.constructor = SubType SubType.prototype.sayAge = function (){ console.log(this.age) } ``` 第一次呼叫SuperType建構函式時,SubType.prototype會得到兩個屬性:name和colors。他們都是SuperType的例項屬性,只不過現在位於SubType的原型中。當呼叫SubType建構函式時,又會呼叫一次SuperType建構函式,這個一次又在新物件上建立了例項屬性name和colors。於是,這兩個屬性就遮蔽了原型中的兩個同名屬性。即有兩組name和colors屬性:一組在例項上,一組在原型上。這就是呼叫兩次SuperType建構函式的結果。解決這個問題的方法就是————寄生組合式繼承。 所謂寄生組合式繼承,即通過借用建構函式來繼承屬性,通過原型鏈的混成形式來繼承方法。其背後的基本思路是:不必為了制定子型別的原型而呼叫超型別的建構函式,我們所需要的無非就是超型別原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超型別的原型,然後再將結果指定給子型別的原型。寄生組合式繼承的基本模式如下: ``` js function object(o) { function F (){} F.prototype = o; return new F() } function inheritPtototype(subType, superType){ var prototype = object(superType.prototype) // 建立物件 prototype.constructor = subType // 增強物件 subType.prototype = prototype // 指定物件 } function SuperType(name){ this.name = name this.colors = ['red', 'white'] } SuperType.prototype.sayName = function(){ console.log(this.name) } function SubType(name,age){ SuperType.call(this,name) this.age = age } inheritPtototype(SubType, SuperType) SubType.prototype.sayAge = function(){ console.log(this.age) } var instance = new SubType('ccc', 18) instance.sayName() // --> ccc instance.sayAge() // --> 18 console.log(instance) ``` 控制檯打印出的結構: ![](https://img2020.cnblogs.com/blog/1950998/202007/1950998-20200718170513548-467317780.png) 詳細的圖解: ![](https://img2020.cnblogs.com/blog/1950998/202007/1950998-20200718170646518-547921318.png) 這個例子的高效率提現在它值呼叫了一次SuperType建構函式,並且因此避免了在SubType.prototype上面建立不必要的、多餘的屬性。與此同時,原型鏈還能保持不變;因此,還能夠正常使用instanceof和isPrototypeOf()。這也是很多大廠用的繼承