繼承
,是面嚮對象語言的一個重要概念。
一般有這兩種繼承方式:接口繼承
和實現繼承
。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。面試
《JS高程》裏提到:「因爲函數沒有 簽名,在‘ECMAScript’中沒法實現 接口繼承。」
等等,函數簽名是什麼東西?據MDN文檔定義以下:segmentfault
函數簽名(類型簽名、方法簽名)定義了函數或方法的輸入與輸出。數組
簽名可包含如下內容:函數
- 參數 及參數的 類型
- 一個的返回值及其類型
- 可能會拋出或傳回的異常
- 該方法在 面向對象程序中的可用性方面的信息(如public、static或prototype)。
看起來好複雜啊,咱們換成強類型的語言的角度會不會更好理解?
譬如,C的函數簽名就是咱們熟悉的函數聲明:this
int func(double d);
此時:參數名爲d
,參數類型爲double
,返回值爲func(d)
,返回值類型爲int
。
再如,Java的函數簽名:prototype
public static void main(String[] args)
此時:參數名爲args
,參數類型爲String []
,返回值類型爲void
因此該方法沒有返回值,訪問修飾符public
表示該方法是公有方法,static
表示該方法是一個類方法而非實例方法……設計
如今,咱們知道函數簽名是怎麼回事了。那麼,接口繼承又是什麼東西?相信你們會遙想起Java中的interface
,implements
等……在此就不班門弄斧了。code
咱們知道,JavaScript是類型鬆散
的語言,不像Java它們有嚴格的變量類型檢查。因此,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.prototype); //建立對象 超類型原型的副本 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); };
extends
關鍵字用來建立一個普通類或者內建對象的子類。
class A { ... } class B extends A { ... }
其中,
B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true
由於extends
實現了:
Object.setPrototypeOf(B.prototype, A.prototype); //B.prototype.__proto__ = A.prototype; Object.setPrototypeOf(B, A); //B.__proto__ = A
extends
更具體的實現方法參見面試官問:JS的繼承,在此就不班門弄斧了~
完~如有不足,請多指教,不勝感激!
以上代碼借鑑於《JS高級程序設計》