long long ago, 在JS王國里,有一個國王,他覺得世界上最美妙的聲音就是鴨子的叫聲,於是國王召集大臣,要組建一個1000只鴨子組成的合唱團。大臣們找遍了全國,終於找到999只鴨子,但是始終還差一只,最后大臣發現有一只非常特別的雞,它的叫聲跟鴨子一模一樣,於是這只雞就成為了合唱團的最后一員。
於是大家定義了鴨子類型,“如果它走起來像鴨子,而且叫起來像鴨子,那么它就是鴨子”。
用JS模擬這個故事:
var duck = function(){ //鴨子
this.duckSinging = function(){
return "嘎";
}
}
var chicken = function(){ //雞
this.duckSinging = function(){
return "嘎";
}
}
var choir = []; //合唱團
var joinChoir = function(animal){ //加入合唱團的方法
if(animal && animal.duckSinging() === "嘎"){
choir.push(animal);
console.log("恭喜加入合唱團");
}
}
for(var i =0;i<999;i++){ //加入999只鴨子
joinChoir(new duck());
}
joinChoir(new chicken());
console.log(choir.length); //1000 現在湊齊了1000只可以嘎嘎叫的動物,不用管它是不是鴨子
可以看到,大臣無需檢查動物的類型,只需保證它們擁有duckSinging方法,無論它是貓狗
javascript作為動態類型語言對變量類型的寬容給實際編碼帶來了很大的靈活性。由於無需進行類型檢測,我們可以嘗試調用任何對象的任意方法,而無需去考慮它原本是否被設計為擁有該方法。
利用鴨子類型的思想,我們不必借助超類型的幫助,就能輕松地在動態類型語言中實現一個原則:“面向接口編程,而不是面向實現編程”。例如,一個對象若有push和pop方法,並且這些方法提供了正確的實現,它就可以被當作棧來使用。一個對象如果有length屬性,也可以依照下標來存取屬性(最好還要擁有slice和splice等方法),這個對象就可以被當作數組來使用。
在靜態類型語言中,要實現“面向接口編程”並不是一件容易的事情,往往要通過抽象類或者接口等將對象進行向上轉型。當對象的真正類型被隱藏在它的超類型身后,這些對象才能在類型檢查系統的“監視”之下互相被替換使用。只有當對象能夠被互相替換使用,才能體現出對象多態性的價值。
我們將偽數組轉換為數組的方法也是鴨子類型:
var arr = Array.prototype.slice.apply({0:1,1:2,2:3,length:3});
console.log(Array.isArray(arr)); //true
可以理解為:偽數組不是“鴨子”,但利用動態語言可以給對象添加方法的特性,賦予偽數組鴨子屬性,也就可以認為偽數組是鴨子了。
鴨子類型中,對象有效的語義,不是繼承自特定的接口或者實現特定的接口,而是有當前方法和屬性的集合決定。
underscore、jquery中用到了一些鴨子類型
參考:http://book.51cto.com/art/201505/475153.htm
https://www.cnblogs.com/happyframework/p/3239790.html