Quiz1
Javascript真的需要類別(Class)麼? 我們先看下其他有類別(Class)的物件導向語言(如:Java)的一些特性。
父類別與子類別 父類別(Superclass)和子類別(Subclass),並不是為了解決父親與兒子的問題,而是為了解決類別的包含關係的,我們用Sub表示“子類”,用Sup表示“父類”,則有:
Sub Sup
這是有區別的,例如通常我們能夠將子類當成父類來使用,但認人的時候我們並不能把兒子當成父親。
或者可以這麼說,父類別和子類別不是為了解決類別間存在相同方法或屬性的。
舉個例子 有人喜歡這樣做:
我們需要一些動物的類,以便在屏幕上創建一些移動的動物,但移動的動物有些在空中飛行,有些在路上行走。
所以建立兩個父類,一個是Fly,一個是Walk:
Class Fly{
Fly(){}
}
Class Walk{
Walk(){}
}
然後獅子
}
}
然後獅子們(還可以再建些其他的在路上行走的動物)就屬於Walk類,老鷹們(也還可以再建些其他在天上飛行的動物)就屬於Fly類:
複製程式碼
程式碼如下:
Class Lion extend Walk{
}
Class Eagle extend Fly{
}
}
Class Eagle extend Fly{ >
最後對Lion和Eagle類別建立一些實例,呼叫對應的方法,螢幕上就會有一些獅子和老鷹在移動了。
但這可能不是一個好的設計,比如明天老闆突然一拍大腦,他要有一種叫天馬(Pegasus)的動物,它們即會在天上飛,又會在路上走,時而要飛行,時候要行走。
在這種情況下,這個方案就全然無用了。
為什麼這個設計失敗了?
組合
我們可以這樣解決這個問題:
複製程式碼
複製程式碼
代碼如下:
Class Lion{
walker = new Walk();
walk(){
return walker.walk();
}
}
Class Eagle{ flyer = new Fly(); fly(){ return flyer.fly(); } } Class Pegasus{ walker = new Walk(); flyer = new Fly(); walk(){ return walker.walk(); } fly(){ return flyer. fly(); } }
組合是簡單的在新類別內部建立原有類別物件。所以組合才是為了解決類別間具有相同的方法的。在這個例子裡面:
Walk被當成“會行走的動物應該擁有的方法集合”,同理Fly被當成“會行走的動物應該擁有的方法集合”,所以對於天馬(Pegasus),我們只需要對Walk和Fly進行組合就行了。
繼承的目的
繼承並非程式碼多用的唯一方法,但繼承有他的優勢:
子類別可以向上轉換變成父類別。
這樣我們就可以忽略所有的子類別差異,當成相同的類別來操作,例如:
我們有方法fn(A),fn(B),這兩個方法實際上是相似的,我們想復用他們。
則我們可以透過設立一個父類C,其中A是C的子類,B是C的子類,那麼fn(C)就可以重複使用在A和B身上了。
回到Javascript 但回到Javascript,我們發現上面的範例是不成立的。
因為Javascript本身就是一種弱型別語言,它並不會在操作前(因為他不用編譯)專注在自己操作的物件類型是什麼。他只會執行成功,或發生錯誤。
這時候,繼承顯得並不必要了。那麼類別也就同樣不是必要的了。
I have been writing JavaScript for 8 years now, and I have never once found need to use an uber function. The super idea is fairly important in the classical pattern, but it appears to be unnecessary in the protox I now see my early attempts to support the classical model in JavaScript as a mistake.
——Douglas Crockford
我寫Javascript程式碼已經8年了,但我從來沒有發現需要使用超類別函數。超類的想法在古典設計模式是非常重要的,但這在以原型和函數為基調的模式中並不必要。我現在覺得,早期我試著讓Javascript支援經典模式是個錯誤的決定。
安全環境
當然,你可以手動去判斷類型,控制參數的類型,進而提供一個較為安全的環境。
例如同樣作為弱型別腳本語言的PHP,為了模擬強型態物件導向語言設定安全環境,只好這麼做:
class ShopProductWriter{
public function write( $shopProduct ){
if( ! ( $Product nce 廣告購買 instact duct) &Product ) ){
die( "輸入錯誤的類型" );
}
//如果類型正確就執行一些代碼
}
}
— —PHP Objects, Patterns, and Practtice Third Edition . Matt Zandstra
但這只是一個非常醜陋的方案而已。
經典繼承語法糖實現 不過經典繼承依然是許多人喜歡的方式。所以YUI、Prototype、Dojo、MooTools都提供了自己的實作方案。
其中較為常見的方案中,語法大概是這樣的:
var Person = Class.extend({
init: function(isDancing){
this.dancing = isDancing;
}
});
var Dancer = Person.extend ({
init: function(){
this._super( true );
}
});
var n = new Dancer();
alert(n.dancing ); //true
最重要的實現是對this._super的實現,其實extend函數只是將傳進來的對象重新組裝了一下,變成一個原型對象,新構造函數的prototype裡。
具體實作請查看參考文獻1。
ECMAScript 6的經典繼承語法糖 對於類別庫各自實現,導致經典繼承語法眾多,ECMA組織貌似不太滿意,他們試圖在ECMAScript 6中加入更加直觀的經典繼承語法糖:
class Amal Amal {
class Amal Amal { constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
bark() {
console.log("Woof!");
}
console.log("Woof!"); } }
總結 實際上,Javascript中經典繼承並不是必要的。
然而基於許多人喜歡經典繼承模型,所以在新版的ECMAScript 6中提供了相關語法糖。
不過在中國,前端想廣泛使用該語法糖應該是一個遙遠的故事…
Quiz2 那Javascript特有的繼承呢?
原型繼承 原型繼承不是解決經典繼承中的集合包含關係的,實際上原型繼承是解決從屬關係的,用數學表達就是:
Sub. prototype ∈ Sup
子級建構函式(子型別)原型是父級建構子(父型別)建構的實例物件。原型實際上是子類型實例中需要共享的東西:
function Being(){
this.living = true;
}
Being.prototype.walk = function(){
alert("I' m walking");
};
function Dancer(){
this.dancing = true;
}
Dancer.prototype = new Being();
Dancer.prototype.dance = function(){
alert ("I'm dancing");
};
var one = new Dancer();
one.walk();
one.dance();
利用借用、寄生等技術可以產生許多不同的繼承效果,但這些技術都只是為了解決原型繼承中屬性和方法一些公用與非公用的問題罷了。由於篇幅問題就不展開討論了,有興趣可以參考《Javascript高級程式設計》相關內容。
思考題 1.文章開頭關於天馬(Pegasus)的題目,如果在Javascript上,應該如何設計呢?例如我們有下列兩種:
function Walk() {
this.walk = function(){
//walk
};
}
function Fly(){
this.fly = function(){
/ /fly
};
}