原型鏈繼承
原理
本質是重寫原型物件,代之以一個新型別的範例。下面程式碼中,原來存在於SuperType的範例物件的屬性和方法,現在也存在於SubType.prototype中了。
實現
function Super(){ this.value = true; } Super.prototype.getValue = function(){ return this.value } function Sub(){}; // Sub繼承了Super Sub.prototype = new Super(); Sub.prototype.constructor = Sub; const ins = new Sub(); console.log(ins.getValue()); // true
Sub繼承了Super,而繼承是通過建立Super範例,並將範例賦給Sub.prototype實現的。原來存在於Super的範例中的所有屬性和方法,現在也存在與Sub.prototype中。如圖所示。
上圖可以看出,沒有使用Sub預設提供的原型,而是給它換了一個新原型;這個新原型就是Super的範例。於是,新原型不僅具有作為一個Super的範例所擁有的屬性和方法,而且它還指向了Super的原型。最終結果就是這樣的:
ins=>Sub的原型=>Super的原型
getValue()方法仍然還在Sub.prototype中,但value屬性則位於Sub.prototype中。這是因為value是一個範例屬性,而getValue()則是一個原型方法。既然Sub.prototype現在是Super的範例,那麼value位於該範例中。
此外,要注意ins.constructor現在指向的是 Super,這是因為原來 Sub.prototype 中的 constructor 被重寫了的緣故。
缺點
私有原型屬性會被範例共用
在建立子型別的範例時,不能向父類別型的建構函式傳遞引數
原型鏈繼承最主要的問題:私有原型屬性會被範例共用,而這也正是為什麼要在建構函式中,而不是原型物件中定義屬性的原因。在通過原型來實現繼承時,原型範例會變成另一個類的範例。於是,原先的範例屬性也就順理成章的變成了現在的原型屬性了。
function Super(){ this.colors = ['red','green','blue']; } Super.prototype.getValue = function(){ return this.colors } function Sub(){}; //Sub繼承了Super Sub.prototype = new Super(); const ins1 = new Super(); ins1.colors.push('black'); console.log(ins1.colors);//['red','green','blue','black']; const ins2 = new Sub(); console.log(ins2.colors);//['red','green','blue','black'];
原型鏈的第二個問題,在建立子型別的範例時,不能向父類別型的建構函式傳遞引數。實際上,應該說是沒有辦法在不影響所有都想範例的情況下,給父類別型的建構函式傳遞引數。再加上包含參照型別值的原型屬性會被所有範例共用的問題,在實踐中很少會單獨使用原型鏈繼承
注意問題
使用原型鏈繼承方法要謹慎地定義方法,子型別有時候需要重寫父類別的某個方法,或者需要新增父類別中不存在的某個方法。但不管怎樣,給原型新增方法的程式碼一定要放在替換原型的語句之後。
function Super() { this.colors = ['red', 'green', 'blue']; } Super.prototype.getValue = function() { return this.colors } function Sub() { this.colors = ['black']; }; //Sub繼承了Super Sub.prototype = new Super(); //新增父類別已存在的方法,會重寫父類別的方法 Sub.prototype.getValue = function() { return this.colors; } //新增父類別不存在的方法 Sub.prototype.getSubValue = function(){ return false; } const ins = new Sub(); //重寫父類別的方法之後得到的結果 console.log(ins.getValue()); //['black'] //在子類中新定義的方法得到的結果 console.log(ins.getSubValue());//false //父類別呼叫getValue()方法還是原來的值 console.log(new Super().getValue());//['red', 'green', 'blue']
借用建構函式繼承
原理
借用建構函式(有時候也叫做偽類繼承或經典繼承)。這種技術的基本思想相當簡單,即在子類建構函式的內部呼叫父類別建構函式。別忘了,函數只不過是在特定環境中執行程式碼的物件,因此通過使用apply()和call()方法也可以在新建立的物件上執行建構函式。
實現
function Super() { this.colors = ['red', 'green', 'blue']; } Super.prototype.getValue = function(){ return this.colors; } function Sub(){ //繼承了Super Super.call(this);//相當於把建構函式Super中的this替換成了ins範例物件,這樣在Super只有定義的私有屬性會被繼承下來,原型屬性中定義的公共方法不會被繼承下來 } const ins = new Sub(); console.log(ins.colors);
傳遞引數:相對於原型鏈來講,借用建構函式繼承有一個很大的優勢,即可以在子類建構函式中向父類別建構函式傳遞引數
function B(name){ this.name = name; } function A(){ //繼承了B,同時還傳遞了引數 B.call(this,'ZZ'); //範例屬性 this.age = 100; } const p = new A(); alert(p.name);//'ZZ' alert(p.age);//100
缺點
如果僅僅是借用建構函式,那麼將無法避免建構函式模式存在的問題——方法都在建構函式中定義,因此函數複用就無從談起。而且,在父類別的原型中定義的方法,對子類而言是不可見的,所以這種方式使用較少。
組合繼承
原理
組合繼承,指的是將原型鏈和借用建構函式技術組合到一起,從而發揮兩者之長的一種繼承模式。其背後的思想是使用原型鏈實現對原型上的公共屬性和方法的繼承,而通過借用建構函式繼承來實現對父類別私有屬性的繼承。這樣,即通過在父類別原型上定義方法實現了函數複用,又能夠保證每個範例都有父類別的私有屬性。
實現
function Super(name){ this.name = name; this.colors = ['red','blue','green']; } Super.prototype.sayName = function(){ alert(this.name); } function Sub(name,age){ Super.call(this,name); this.age = age; } // 繼承方法 Sub.prototype = new Super(); Sub.prototype.constructor = Sub; Sub.prototype.sayAge = function(){ alert(this.age); } const ins = new Sub('jarvis',18); ins.colors.push('black'); console.log(ins.colors);// ["red", "blue", "green", "black"] ins.sayName();//'jarvis' ins.sayAge();//18 const ins2 = new Sub('ershiyi',21); console.log(ins2.colors);//["red", "blue", "green"] ins2.sayName();//'ershiyi' ins2.sayAge();//21
在上個例子中,Sub建構函式定義了兩個屬性:name和age。Super的原型定義了一個sayName()方法。在Sub建構函式中呼叫Super建構函式時傳入了name引數,緊接著又定義它自己的屬性age。然後,將Super的範例賦值給Sub的原型,然後又在該新原型上定義了方法sayAge()。這樣一來,就可以讓不同的Sub範例分別擁有自己的屬性——包括colors屬性,又可以使用相同的方法組合繼承避免了原型鏈和借用建構函式的缺陷,融合了他們的優點,稱為JavaScript中最常用的繼承模式。
缺點
無論在什麼情況下,都會呼叫兩次父類別的建構函式:一次是在建立子類原型的時候,另一次是在子類建構函式內部。
寄生組合式繼承
原理
組合繼承是JavaScript最常用的繼承模式;不過,它也有自己的不足。組合繼承最大的問題就是無論什麼情況下,都會呼叫兩次父類別建構函式:一次是在建立子類原型的時候,另一次是在子類建構函式內部。沒錯,子型別最終會包含超型別物件的全部範例屬性,但不得不在呼叫子型別建構函式時重寫這些屬性。再來看一看下面組合繼承的例子。
實現
function Super(name){ this.name = name; this.colors = ['red','blue','green']; } Super.prototype.sayName = function(){ alert(this.name); } function Sub(name,age){ Super.call(this,name); this.age = age; } // 繼承方法 Sub.prototype = new Super(); Sub.prototype.constructor = Sub; Sub.prototype.sayAge = function(){ alert(this.age); } const ins = new Sub('jarvis',18); ins.colors.push('black'); console.log(ins.colors);// ["red", "blue", "green", "black"] ins.sayName();//'jarvis' ins.sayAge();//18 const ins2 = new Sub('ershiyi',21); console.log(ins2.colors);//["red", "blue", "green"] ins2.sayName();//'ershiyi' ins2.sayAge();//21
所謂寄生組合式繼承,即通過借用建構函式來繼承屬性,通過原型鏈的混成形式來繼承方法。其背 後的基本思路是:不必為了指定子型別的原型而呼叫超型別的建構函式,所需要的無非就是超型別原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超型別的原型,然後再將結果指定給子型別的原型。寄生組合式繼承的基本模式如下所示。
function Super(name){ this.name = name; this.colors = ['red','blue','green']; } Super.prototype.sayName = function(){ alert(this.name); } function Sub(name,age){ //繼承範例屬性 Super.call(this,name); this.age = age; } // 繼承公有的方法 Sub.prototype = Object.create(Super.prototype); Sub.prototype.constructor = Sub; Sub.prototype.sayAge = function(){ alert(this.age); } const ins = new Sub('jarvis',18); ins.colors.push('black'); console.log(ins.colors);// ["red", "blue", "green", "black"] ins.sayName();//'jarvis' ins.sayAge();//18 const ins2 = new Sub('ershiyi',21); console.log(ins2.colors);//["red", "blue", "green"] ins2.sayName();//'ershiyi' ins2.sayAge();//21
多重繼承
JavaScript中不存在多重繼承,那也就意味著一個物件不能同時繼承多個物件,但是可以通過變通方法來實現。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>18 多重繼承</title> </head> <body> <script type="text/javascript"> // 多重繼承:一個物件同時繼承多個物件 // Person Parent Me function Person(){ this.name = 'Person'; } Person.prototype.sayName = function(){ console.log(this.name); } // 客製化Parent function Parent(){ this.age = 30; } Parent.prototype.sayAge = function(){ console.log(this.age); } function Me(){ // 繼承Person的屬性 Person.call(this); Parent.call(this); } // 繼承Person的方法 Me.prototype = Object.create(Person.prototype); // 不能重寫原型物件來實現 另一個物件的繼承 // Me.prototype = Object.create(Parent.prototype); // Object.assign(targetObj,copyObj) Object.assign(Me.prototype,Parent.prototype); // 指定建構函式 Me.prototype.constructor = Me; const me = new Me(); </script> </body> </html>
ES5 與 ES6 繼承差異
在 ES5 的傳統繼承中, this 的值會先被派生類建立,隨後基礎類別構造器才被呼叫。這意味著 this 一開始就是派生類的範例,之
後才使用了基礎類別的附加屬性對其進行了裝飾。
在 ES6 基於類的繼承中, this 的值會先被基礎類別建立,隨後才被派生類的構造 器所修改。結果是 this 初始就擁有作為基礎類別的內建物件的所有功能,並能正確接收與之關聯的所有功能。
【相關推薦:】
以上就是十分鐘帶你瞭解JavaScript的繼承的詳細內容,更多請關注TW511.COM其它相關文章!