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