ECMAScript类型检测
JS 类型判断为什么有这么多方法——它们不是重复,而是回答不同维度的问题。
#type / synthesis
#status / evergreen
#resource / javascript
#resource / ecmascript
[!info] related notes
ECMAScript类型检测
一句话核心
JS 的”类型”至少有 4 个维度,不同判断方法各自回答不同问题,没有万能 API。
JS 到底有哪些类型
原始类型(Primitive)
undefined · null · boolean · number · string · symbol · bigint
对象类型(Object)
普通对象、数组、函数、日期、正则、Map、Set、Error、Promise 等——在规范层面它们都是对象,只是带有特殊内部行为。
数组不是独立基础类型。 它是带有特殊内部槽的对象,所以 typeof [] === "object"。
“类型”的 4 个维度
| 维度 | 问的问题 | 代表方法 |
|---|---|---|
| 语言基础类型 | 这个值属于哪一大类? | typeof |
| 具体内建结构 | 是不是数组 / 日期 / 正则? | array-isarray, object-prototype-tostring |
| 构造器实例关系 | 是不是某个类 / 构造器创建的? | instanceof |
| 行为能力 | 能不能调用 / 迭代 / thenable? | duck-typing |
为什么不能统一成一个 API
类比问人的身份:
typeof:你是自然人 / 公司 / 组织?instanceof:你是不是某学校毕业的?Array.isArray:你是不是专业运动员?toString.call:你在官方系统里登记的是什么类别?- 鸭子类型:你会不会游泳?
都叫”判断身份”,但维度不同。JS 不是没有统一类型系统,而是类型本身就分成了多个维度。
各方法速查
typeof — 判断基础运行时类别
typeof 123 // "number"
typeof "abc" // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object"(历史 bug)
typeof [] // "object"
typeof {} // "object"
typeof function(){} // "function"
优点:简单、快、对原始类型好用、typeof x 不会因 x 未声明而报错。
缺点:null 是坑,不能细分对象。
instanceof — 判断原型链归属
[] instanceof Array // true
[] instanceof Object // true
123 instanceof Number // false(原始值不是对象)
判断的是”右边构造器的 prototype 是否出现在左边对象的原型链上”。
缺点:跨 iframe 可能失准、不能判断原始值、可被 Symbol.hasInstance 干预。
array-isarray — 数组判断专用
Array.isArray([]) // true
Array.isArray({}) // false
Array.isArray(iframeArray) // true(跨 realm 也稳)
判断的是内部品牌(brand),不是原型链,所以跨 iframe 更可靠。
object-prototype-tostring — 通用精细分类
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call(new Date()) // "[object Date]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
能细分大量内建对象,连 null / undefined 也能区分。
注意:现代对象可用 Symbol.toStringTag 自定义标签。
duck-typing — 判断能力而非身份
// 能不能迭代
typeof obj[Symbol.iterator] === 'function'
// 能不能 thenable
typeof x.then === 'function'
核心思想:不关心它是什么,关心它能做什么。
选型口诀
| 场景 | 推荐 |
|---|---|
| 判断原始类型 | typeof |
判断 null | x === null |
同时判断 null 或 undefined | x == null |
| 判断数组 | Array.isArray(x) |
| 判断类实例 | x instanceof MyClass |
| 细分内建对象 | Object.prototype.toString.call(x) |
| 判断能力(可迭代 / thenable) | 鸭子类型 |
判断 NaN | Number.isNaN(x) |
边界情况速查
| 场景 | typeof | instanceof | toString.call | 推荐 |
|---|---|---|---|---|
null | "object" | TypeError | "[object Null]" | === null |
NaN | "number" | — | "[object Number]" | Number.isNaN() |
[] | "object" | true | "[object Array]" | Array.isArray() |
| 跨 iframe 数组 | "object" | false | "[object Array]" | Array.isArray() |
document.all | "undefined" | false | "[object HTMLAllCollection]" | x !== undefined |
组合判断工具函数
function getType(value) {
if (value === null) return 'null';
const type = typeof value;
if (type !== 'object') return type;
const tag = Object.prototype.toString.call(value);
return tag.slice(8, -1).toLowerCase();
}
// getType(null) → "null"
// getType([]) → "array"
// getType(new Date()) → "date"
// getType(/a/) → "regexp"
思路:原始类型用 typeof,null 特判,对象细分类用 toString.call。
为什么 JS 会变成这样
- 早期设计求快不求精:初版开发周期极短,很多实现细节直接成为语言行为
- 原型式对象系统:不是类式继承,天然更强调行为能力和运行时灵活性
- 向后兼容压倒一切:
typeof null === "object"修不了,因为已有海量代码依赖 - 增量修补式演进:旧 API 不改,新需求来了就补新 API(
Array.isArray、Number.isNaN)