JavaScript之繼承(不看白不看)
一、繼承的概念
繼承
,是面嚮物件語言的一個重要概念。
通常有這兩種繼承方式:介面繼承
和實現繼承
。介面繼承只繼承方法簽名,而實現繼承則繼承實際的方法。
《JS高程》裡提到:“由於函式沒有簽名 ,在‘ECMAScript’中無法實現介面繼承 。”
等等,函式簽名是什麼東西?據MDN文件 定義如下:
函式簽名(型別簽名、方法簽名)定義了函式或方法的輸入與輸出。
簽名可包含以下內容:
- 引數 及引數的 型別
- 一個的返回值及其型別
- 可能會丟擲或傳回的異常
- 該方法在 面向物件程式中的可用性方面的資訊(如public、static或prototype)。
看起來好複雜啊,我們換成強型別的語言的角度會不會更好理解?
譬如,C的函式簽名就是我們熟悉的函式宣告:
int func(double d);
此時:引數名為d
,引數型別為double
,返回值為func(d)
,返回值型別為int
。
再如,Java的函式簽名:
public static void main(String[] args)
此時:引數名為args
,引數型別為String []
,返回值型別為void
所以該方法沒有返回值,訪問修飾符public
表示該方法是公有方法,static
表示該方法是一個類方法而非例項方法……
現在,我們知道函式簽名是怎麼回事了。那麼,介面繼承又是什麼東西?相信大家會遙想起Java中的interface
,implements
等……在此就不班門弄斧了。
我們知道,JavaScript是型別鬆散
的語言,不像Java它們有嚴格的變數型別檢查。所以,JS的函式才沒有簽名,才無法實現介面繼承。
那麼,JS的實現繼承是怎麼回事呢?
二、JS的繼承
-
原型鏈的基本模式
理解:通過建立
SuperType
的例項,並將該例項賦給SubType.prototype
,來實現SubType
繼承SuperType
。instance
的原型指向SubType
,SubType
的原型指向SuperType
,SuperType
的原型指向Object
,如此構成了原型鏈。缺點:物件例項共享所有繼承的屬性和方法,不適宜單獨適用
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType() { this.subproperty = false; } //SubType繼承SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue());//true
於是,有下一個招式,來解決包含引用型別值的原型屬性會被所有例項共享的弱點。
-
借用建構函式
理解:“借用”超型別構造方法,在新的子型別物件上執行超型別函式定義的所有物件初始化程式碼
適用:解決超型別的引用型別值被所有子型別物件例項共享,而且子型別可向超型別傳參
缺點:不能做到函式複用,從而降低效率,不適宜單獨適用
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; //引用型別值 } function SubType() { SuperType.call(this, "A"); //繼承SuperType “借用”超型別的建構函式 並傳參 this.age = 20;//例項屬性 } var instance1 = new SubType();//instance1.name: "A", instance1.age: 20 instance1.colors.push("black"); //instance1.colors: "red, blue, green, black" var instance2 = new SubType();//instance2.colors: "red, blue, green"
接下來,組合技能出大招!
-
組合繼承
理解:將原型鏈和借用建構函式組合到一起,通過原型鏈來繼承共享的原型屬性和方法,通過借用建構函式來繼承例項屬性。
適用:最常用的繼承模式。
缺點:呼叫兩次超型別建構函式,在SubType上建立了多餘的屬性,造成超型別物件的例項屬性的重寫
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { alert(this.name); }; function SubType(name, age) { SuperType.call(this, name);//繼承屬性 第二次呼叫超型別建構函式 新例項得到兩個例項屬性name,colors this.age = age; } SubType.prototype = new SuperType();//繼承方法 第一次呼叫超型別建構函式 SubType.prototype得到兩個例項屬性name,colors SubType.prototype.sayAge = function() { alert(this.age); }; var instance1 = new SubType("妹妹", 18); instance1.colors.push("black"); alert(instance1.colors);//"red, blue, green, black" instance1.sayName();//"妹妹" instance1.sayAge();//18 var instance2 = new SubType("弟弟", 20); alert(instance2.colors);//"red, blue, green" instance2.sayName();//"弟弟" instance2.sayAge();//20
這是打boss的大招,那我怎麼對付小怪?
-
原型式繼承
理解:本質是對給定物件的淺複製
適用:不必預先定義建構函式來實現繼承,只想讓一個物件與另一個物件保持類似
缺點:引用型別值的屬性被共享,如同原型模式一樣
function object(o) {//對o進行淺複製 function F() {}//建立一個臨時性的建構函式 F.prototype = o;//將傳入的物件o作為F的原型 return new F();//返回F的新例項 } var person = { name: "A", friends: ["B", "C", "D"] }; var anotherPerson = object(person);// Object.create(person) anotherPerson.name = "E"; anotherPerson.friends.push("F"); var yetAnotherPerson = object(person);//Object.create(person) yetAnotherPerson.name = "G"; yetAnotherPerson.friends.push("H"); alert(person.friends);//"B, C, D, F, H" 引用型別值的屬性被共享啦
注意:
Object.create()
方法的第二個引數,新物件的額外屬性的物件,會覆蓋原型物件上的同名屬性。var anotherPerson = Object.create(person, { name: { value: "E" } }); alert(anotherPerson.name);// "E"
打了這麼久,能不能讓小招也升級(封裝)一下啊?
-
寄生式繼承
建立一個僅用於封裝繼承過程的函式,該函式在內部以某種方式來增強物件,最後再像真的是它做了所有工作一樣返回物件。
理解:繼承的工作是通過呼叫函式實現的,所以是“寄生”,將繼承工作寄託給別人做,自己只是做增強工作。
適用:基於某個物件或某些資訊來建立物件,而不考慮自定義型別和建構函式。
缺點:不能做到函式複用,從而降低效率
function createAnother(original) { var clone = object(original); //通過呼叫函式建立一個新物件 clone.sayHi = function() {//以某種方式來增強這個物件 alert("hi"); } return clone; } var person = { name: "A", friends: ["B", "C", "D"] }; var anotherPerson = createAnother(person);//不僅有person所有屬性方法,還有自己的sayHi方法 anotherPerson.sayHi();//"hi"
經驗攢足,我要把之前打boos的組合大招升到滿級!
-
寄生組合繼承
理解:通過借用建構函式來繼承屬性,通過原型鏈的混用形式來繼承方法。用寄生式繼承來繼承超型別的原型,再將增強後的結果指定給子型別的原型。
適用:引用型別最理想的繼承模式,高效,只調用了一個SuperType建構函式
function inheritPrototype(subType, superType) { var prototype = object(superType, subType); //建立物件 超型別原型的副本 prototype.constructor = subType;//增強物件 彌補因重寫原型而失去預設的constructor屬性 subType.prototype = prototype;//指定物件 } function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { alert(this.name); }; function SubType(name, age) { SuperType.call(this, name);//繼承屬性 this.age = age; } inheritPrototype(SubType, SuperType);//繼承方法 SubType.prototype.sayAge = function() { alert(this.age); };
恭喜你,繼承通關啦~~~
完~若有不足,請多指教,不勝感激!
以上程式碼借鑑於《JS高階程式設計》