鸭子类型 (Duck Typing)
鸭子类型不关心值的名义身份,只关心它有没有某种行为能力——能调用、能迭代、能 thenable。
#type / concept
#status / evergreen
#resource / javascript
#resource / ecmascript
[!info] related notes
- 所属关系笔记: ecmascript-type-detection
- 相关概念: ecmascript-iterators-and-generators, Promise
鸭子类型 (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 | 你的内部标签是什么? |
| 鸭子类型 | 你会不会做某件事? |