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
判断 nullx === null
同时判断 nullundefinedx == null
判断数组Array.isArray(x)
判断类实例x instanceof MyClass
细分内建对象Object.prototype.toString.call(x)
判断能力(可迭代 / thenable)鸭子类型
判断 NaNNumber.isNaN(x)

边界情况速查

场景typeofinstanceoftoString.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"

思路:原始类型用 typeofnull 特判,对象细分类用 toString.call

为什么 JS 会变成这样

  1. 早期设计求快不求精:初版开发周期极短,很多实现细节直接成为语言行为
  2. 原型式对象系统:不是类式继承,天然更强调行为能力和运行时灵活性
  3. 向后兼容压倒一切typeof null === "object" 修不了,因为已有海量代码依赖
  4. 增量修补式演进:旧 API 不改,新需求来了就补新 API(Array.isArrayNumber.isNaN
创建于 2025/1/1 更新于 2026/5/27