我们可以通过一个小故事来更深刻地了解鸭子类型。
从前在 JavaScript王国里,有一个国王,他觉得世界上最美妙的声音就是鸭子的叫 声,于是国王召集大臣,要组建一个 1000 只鸭子组成的合唱团。大臣们找遍了全国, 终于找到 999只鸭子,但是始终还差一只,最后大臣发现有一只非常特别的鸡,它的叫 声跟鸭子一模一样,于是这只鸡就成为了合唱团的最后一员。

这个故事告诉我们,国王要听的只是鸭子的叫声,这个声音的主人到底是鸡还是鸭并不重要。 鸭子类型指导我们只关注对象的行为,而不关注对象本身,也就是关注 HAS-A, 而不是 IS-A。
var duck = { type: 'duck', duckSinging: function () { console.log('gagagag'); }}var chicken = { type: 'chicken', duckSinging: function () { console.log('gagagag'); }}
var choir = [];
var joinChoir = function (animal) { if (animal && typeof animal.duckSinging === 'function') { choir.push(animal); console.log(`恭喜${animal.type}加入合唱团`); console.log(`合唱团已有人数为${choir.length}`); }}joinChoir(duck);joinChoir(chicken);我们看到,对于加入合唱团的动物,大臣们根本无需检查它们的类型,而是只需要保证它们 拥有 duckSinging 方法。如果下次期望加入合唱团的是一只小狗,而这只小狗刚好也会鸭子叫, 我相信这只小狗也能顺利加入。
在动态类型语言的面向对象设计中,鸭子类型的概念至关重要。利用鸭子类型的思想,我们 不必借助超类型的帮助,就能轻松地在动态类型语言中实现一个原则:“面向接口编程,而不是 面向实现编程*”。例如,一个对象若有 push 和 pop 方法,并且这些方法提供了正确的实现,它就 可以被当作栈来使用。一个对象如果有 length 属性,也可以依照下标来存取属性(最好还要拥 有 slice 和 splice 等方法),这个对象就可以被当作数组来使用。
在静态类型语言中,要实现“面向接口编程”并不是一件容易的事情,往往要通过抽象类或 者接口等将对象进行向上转型。当对象的真正类型被隐藏在它的超类型身后,这些对象才能在类 型检查系统的“监视”之下互相被替换使用。只有当对象能够被互相替换使用,才能体现出对象 多态性的价值。
“面向接口编程”是设计模式中最重要的思想,但在 JavaScript语言中,“面向接口编程”的 过程跟主流的静态类型语言不一样,因此,在 JavaScript中实现设计模式的过程与在一些我们熟 悉的语言中实现的过程会大相径庭。