許多OO 語言都支援兩種繼承方式:介面繼承和實作繼承。介面繼承只繼承方法簽名,而實作繼承則繼承實際的方法。如前所述,由於函數沒有簽名,在ECMAScript 中無法實現介面繼承。 ECMAScript 只支援實現繼承,實現繼承主要是依靠原型鏈來實現的。在此,主要闡述一下,原型鏈繼承,借用建構子、組合繼承、原型式繼承、寄生式繼承、寄生組合繼承等。
1.原型鏈
ECMAScript 中描述了原型鏈的概念,並將原型鏈作為實現繼承的主要方法。其基本思想是利用原型讓一個引用型別繼承另一個引用型別的屬性和方法。簡單回顧一下建構子、原型和實例的關係:每個建構函式都有一個原型對象,原型對像都包含一個指向建構函式的指針,而實例都包含一個指向原型對象的內部指針。那麼,假如我們讓原型物件等於另一個類型的實例,結果會怎麼樣呢?顯然,此時的原型物件將包含一個指向另一個原型的指針,相應地,另一個原型中也包含一個指向另一個建構函數的指針。假如另一個原型又是另一個類型的實例,那麼上述關係依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。
function Person(){ this.name=”defaultName”; } Person.property.doAction=function(){ alert(“talk”); } function Student(){ this.age=5; } Student.property=new Person(); Student.property.doSome=function(){ alert(“ homework”); }
原型鏈雖然很強大,可以用它來實現繼承,但它也存在一些問題。其中,最主要的問題來自包含引用類型值的原型。想必大家還記得,我們前面介紹過包含引用類型值的原型屬性會被所有實例共享;而這也正是為什麼要在建構函數中,而不是在原型物件中定義屬性的原因。透過原型來實現繼承時,原型實際上會變成另一個類型的實例。於是,原先的實例屬性也就順理成章地變成了現在的原型屬性了。原型鏈的第二個問題是:在建立子類型的實例時,不能傳遞參數給超類型的建構函式。實際上,應該說是沒有辦法在不影響所有物件實例的情況下,給超類型的建構函式傳遞參數。有鑑於此,再加上前面剛剛討論過的由於原型中包含引用類型值所帶來的問題,實踐中很少會單獨使用原型鏈。
2.借用建構子
在解決原型中包含引用型別值所帶來問題的過程中,開發者開始使用一種叫做借用建構函式(constructor stealing)的技術(有時候也叫做偽造物件或經典繼承)。這種技術的基本思想相當簡單,即在子類型構造函數的內部呼叫超類型構造函數。別忘了,函數只不過是在特定環境中執行程式碼的對象,因此透過使用apply()和call()方法也可以在(將來)新建立的對像上執行建構函式。
function Person(name){ this.name=name; } Person.property.doAction=function(){ alert(“talk”); } Person.property.showName=function(){ alert(this.name); } function Student(){ Person.call(this,name); this.age=5; }
如果只是藉用建構函數,那麼也將無法避免建構函數模式存在的問題-方法都在建構函式中定義,因此函數複用就無從談起了。 而且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結果所有型別都只能使用建構子模式。考慮到這些問題,借用構造函數的技術也是很少單獨使用的。
3.組合繼承
組合繼承(combination inheritance),有時候也叫做偽經典繼承,指的是將原型鍊和借用構造函數的技術組合到一塊,從而發揮二者之長的一種繼承模式。背後的想法是使用原型鏈實現對原型屬性和方法的繼承,而藉由借用建構函式來實現實例屬性的繼承。這樣,既透過在原型上定義方法實現了函數復用,又能夠保證每個實例都有它自己的屬性。
function Person(name){ this.name=name; this.loves=[“sing”,”paly games”] } Person.property.showLoves=function (){ alert(this.lovers); } function Student(name,age){ Person.class(this,name); This.age=age; } Student.property=new Person(); Student.property.constructor=Student; Student.property.showName=function(){ alert(this.name); }
組合繼承避免了原型鍊和借用建構函數的缺陷,融合了它們的優點,成為JavaScript 中最常用的繼承模式。而且,instanceof 和isPrototypeOf()也能夠用來識別基於組合繼承所建立的物件。
4.原型式繼承
function object(o){ function F(){} F.prototype = o; return new F(); }
在沒有必要興師動眾地創建建構函數,而只想讓一個物件與另一個物件保持類似的情況下,原型式繼承是完全可以勝任的。不過別忘了,包含引用類型值的屬性總是會共用對應的值,就像使用原型模式一樣。
5.寄生組合式繼承
所謂寄生組合式繼承,即藉由借用建構函式來繼承屬性,透過原型鏈的混成形式來繼承方法。其背
後的基本想法是:不必為了指定子類型的原型而呼叫超類型的建構函數,我們所需要的無非就是超型別
原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,然後再將結果指定給子類型
的原型。寄生組合式繼承的基本模式如下圖所示。
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; //指定对象 }
个参数:子类型构造函数和超类型构造函数。在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加constructor 属性,从而弥补因重写原型而失去的默认的constructor 属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。这样,我们就可以用调用inheritPrototype()函数的语句,去替换前面例子中为子类型原型赋值的语句了。
集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。
以上就是JavaScript面向对象编程(继承实现方式)的内容,更多相关内容请关注PHP中文网(www.php.cn)!