鸭子类型 (Duck Typing)

鸭子类型不关心值的名义身份,只关心它有没有某种行为能力——能调用、能迭代、能 thenable。

#type / concept #status / evergreen #resource / javascript #resource / ecmascript

[!info] related notes

鸭子类型 (Duck Typing)

一句话定义

如果它走起来像鸭子,叫起来像鸭子,那就把它当鸭子。

不判断”它是什么”,而是判断”它能不能做某件事”。

核心思想

JS 是动态语言,很多时候你真正关心的不是”它是不是 Array”,而是”它能不能 for…of”、“能不能调用”、“能不能当 Promise 用”。

这就是 duck typing:通过行为而非名义身份来判断。

常见用法

判断可迭代

function isIterable(obj) {
  return obj != null && typeof obj[Symbol.iterator] === 'function';
}

isIterable([1, 2])         // true
isIterable('hello')        // true
isIterable(new Map())      // true
isIterable({})             // false
isIterable(123)            // false

判断 Promise-like(thenable)

function isThenable(x) {
  return x != null && typeof x.then === 'function';
}

isThenable(Promise.resolve())  // true
isThenable({ then: () => {} }) // true(不是真正的 Promise,但能用)
isThenable({})                 // false

Promise.resolve() 内部就是用 duck typing 判断的:任何有 .then 方法的对象都当 thenable 处理。

判断可调用

typeof fn === 'function'

这其实是 typeof 掺入的一点 duck typing:函数有 [[Call]] 内部能力,typeof 就返回 "function"

判断类数组

function isArrayLike(obj) {
  return obj != null
    && typeof obj.length === 'number'
    && obj.length >= 0
    && Number.isInteger(obj.length);
}

isArrayLike([1, 2, 3])          // true
isArrayLike('hello')            // true
isArrayLike({ length: 3 })      // true
isArrayLike({})                 // false

优点

  • 灵活,更符合 JS 动态特性
  • 对接口 / 协议型设计很自然
  • 不依赖具体构造器或原型链

缺点

  • 可能误判(对象只是”长得像”但语义不一致)
  • 不能表达严格身份
  • 如果协议约定不清晰,可能埋 bug

适用场景

  • 判断值是否有某种协议能力(可迭代、thenable、类数组)
  • 写通用库时需要兼容多种输入
  • 不想强绑定具体构造器类型

与其他方法的关系

方法问的问题
typeof你是什么基础类型?
instanceof你是不是某构造器的实例?
Array.isArray你是不是数组?
toString.call你的内部标签是什么?
鸭子类型你会不会做某件事?
创建于 2026/4/1 更新于 2026/5/27