技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTJS

JS 判断数据类型的 8 种方式

所属馆藏
JavaScript
文件格式
Markdown
原始路径
JavaScript/39-JS 判断数据类型的 8 种方式
本文目录17 个章节
  1. 一、先区分“类型判断”的目标
  2. 二、typeof
  3. 三、constructor
  4. 四、instanceof
  5. 五、手写 instanceof(仅用于理解)
  6. 六、原型查询:isPrototypeOf 与 Object.getPrototypeOf
  7. 七、一些针对性标准 API
  8. 八、Object.prototype.toString
  9. 九、鸭子类型(Duck Typing)
  10. 十、Symbol.toStringTag
  11. 十一、直接比较与特殊值
  12. 十二、NaN 判断
  13. 十三、indexOf 与 includes
  14. 十四、常用选择表
  15. 十五、一个组合式类型辅助函数
  16. 十六、总结
  17. 参考资料

JS 判断数据类型的 8 种方式

Category(分类): JavaScript Status: 已整理(2026)

本文保留原文的八类方法:typeofconstructorinstanceof、原型查询、Object.prototype.toString、鸭子类型、Symbol.toStringTag 和直接比较,并修复抓取导致的代码粘连。新增 bigint、TDZ、跨 Realm、Proxy、Symbol.hasInstanceObject.is 和现代推荐选择。

原文:JS 判断数据类型的 8 种方式

一、先区分“类型判断”的目标

类型判断可能是在回答不同问题:

  • 它是不是某一种原始类型?
  • 它是不是数组、日期或正则?
  • 它是否由某个 class 创建,或原型链上是否存在某个原型?
  • 它是否具备某种能力,例如 then 方法?
  • 它是否是 NaN、有限数字、nullundefined
  • 它是否来自另一个 iframe/Realm?

没有一个 API 能正确回答所有问题。应先明确目标,再选择工具。

ECMAScript 的原始类型包括 undefinednullbooleanstringsymbolnumberbigint;对象是另一类语言类型。typeof 返回的是字符串标签,不是完整的类型系统。

二、typeof

typeof 适合快速判断大多数原始类型和“是否为函数”:

console.log(typeof undefined) // 'undefined'
console.log(typeof null) // 'object':历史兼容行为
console.log(typeof true) // 'boolean'
console.log(typeof 123) // 'number'
console.log(typeof NaN) // 'number'
console.log(typeof 123n) // 'bigint'
console.log(typeof 'hello') // 'string'
console.log(typeof Symbol('id')) // 'symbol'
console.log(typeof {}) // 'object'
console.log(typeof []) // 'object'
console.log(typeof function () {}) // 'function'

2.1 typeof null

typeof null === 'object' 是早期 JavaScript 的历史兼容结果,不能据此把 null 当作普通对象:

function isNull(value) {
  return value === null
}

console.log(isNull(null)) // true
console.log(isNull({})) // false

2.2 typeof document.all

在浏览器中,document.all 是一个特殊的 Web 兼容对象,typeof document.all 可能返回 'undefined',同时它还具有历史上的特殊真假值行为。它不是普通 ECMAScript 对象,也不能作为判断一般对象的依据:

if (typeof document !== 'undefined') {
  console.log(typeof document.all) // 浏览器中的兼容特例
}

2.3 TDZ 中的 typeof 也可能抛错

typeof 对未声明的标识符通常不会抛错,但处于暂时性死区(TDZ)的 letconstclass 绑定会抛出 ReferenceError

try {
  console.log(typeof temporalValue)
  let temporalValue = 1
} catch (error) {
  console.log(error.name) // ReferenceError
}

因此不能用 typeof 绕过所有未初始化变量错误。

三、constructor

对象通常可以沿原型链访问 constructor

const array = [1, 2, 3]
const object = { name: 'Ada' }

console.log(array.constructor === Array) // true
console.log(object.constructor === Object) // true

constructor 不是可靠的类型品牌:

  • nullundefined 没有可访问的 constructor
  • 属性可以被实例自身覆盖;
  • 原型对象可以被替换或篡改;
  • Object.create(null) 没有 Object.prototype
  • 不同 Realm 中的 ArrayObject 构造器不是同一个函数。
const user = {}
user.constructor = 'not a constructor'
console.log(user.constructor) // not a constructor
console.log(user instanceof Object) // true

如果只是想获取对象的原型,使用 Object.getPrototypeOf

console.log(Object.getPrototypeOf(array) === Array.prototype) // true
console.log(Object.getPrototypeOf(object) === Object.prototype) // true

__proto__ 是 legacy accessor,不建议作为现代代码的首选。

四、instanceof

基本形式:

const array = [1, 2, 3]
const object = { name: 'Ada' }

console.log(array instanceof Array) // true
console.log(array instanceof Object) // true
console.log(object instanceof Object) // true
console.log(object instanceof Array) // false

它主要检查:右操作数的 prototype 是否出现在左操作数的原型链上。它不是“这个值曾由哪个构造函数创建”的绝对证明。

4.1 右侧不一定写成 class

右侧必须提供合法的 @@hasInstance 行为。class 和普通函数通常可以;拥有可调用 Symbol.hasInstance 的对象也可以:

const evenNumber = {
  [Symbol.hasInstance](value) {
    return typeof value === 'number' && value % 2 === 0
  }
}

console.log(2 instanceof evenNumber) // true
console.log(3 instanceof evenNumber) // false

因此,“右侧必须是函数或者 class”是过于简化的说法。

4.2 跨 Realm 限制

不同 iframe、Worker 或 VM Realm 中各自拥有不同的内建构造器:

// 浏览器中:otherWindow.Array !== window.Array
// otherWindowArray instanceof Array 可能为 false

跨 Realm 判断数组应使用 Array.isArray,不要使用 value instanceof Array

4.3 原型被修改和 Proxy

instanceof 可能受原型修改、Proxy 和自定义 Symbol.hasInstance 影响;它适合可信代码、同一 Realm 和稳定原型的场景,不适合安全品牌校验。

五、手写 instanceof(仅用于理解)

下面的实现演示原型链遍历,但不等价于原生 instanceof

function myInstanceof(value, Constructor) {
  if (typeof Constructor !== 'function') {
    throw new TypeError('这个教学版本的右侧必须是函数')
  }

  if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
    return false
  }

  const prototype = Constructor.prototype
  if (typeof prototype !== 'object' || prototype === null) {
    throw new TypeError('Constructor.prototype 必须是对象')
  }

  let current = Object.getPrototypeOf(value)
  while (current !== null) {
    if (current === prototype) return true
    current = Object.getPrototypeOf(current)
  }
  return false
}

function User() {}
const user = new User()
console.log(myInstanceof(user, User)) // true
console.log(myInstanceof({}, User)) // false

这个教学版本没有实现:

  • Symbol.hasInstance
  • 右侧 Proxy 的行为;
  • 跨 Realm 特殊情况;
  • 原生算法的全部异常边界。

生产代码应直接使用 instanceof,不要为了“手写面试题”替换原生语义。

六、原型查询:isPrototypeOfObject.getPrototypeOf

isPrototypeOf 判断一个对象是否出现在另一个对象的原型链上:

const object = { name: 'Ada' }
const array = [1, 2, 3]

console.log(Object.prototype.isPrototypeOf(object)) // true
console.log(Array.prototype.isPrototypeOf(array)) // true
console.log(Array.prototype.isPrototypeOf(object)) // false

正确拼写是 isPrototypeOf,不是 isPrototypeof。它的接收者必须是对象,但传入的待检查值为 nullundefined 时会返回 false

console.log(Object.prototype.isPrototypeOf(null)) // false
console.log(Object.prototype.isPrototypeOf(undefined)) // false

与之不同,Object.getPrototypeOf(null)Object.getPrototypeOf(undefined) 才会抛出 TypeError

Object.getPrototypeOf 返回对象的直接原型:

console.log(Object.getPrototypeOf(object) === Object.prototype) // true
console.log(Object.getPrototypeOf(array) === Array.prototype) // true
console.log(Object.getPrototypeOf(Object.create(null)) === null) // true

这两个 API 都是原型关系查询,并不等同于完整的 instanceofinstanceof 还可能调用右侧的 Symbol.hasInstance

七、一些针对性标准 API

7.1 Array.isArray

判断数组应优先使用 Array.isArray

console.log(Array.isArray([1, 2, 3])) // true
console.log(Array.isArray({})) // false
console.log(Array.isArray('array')) // false

它比 instanceof Array 更适合跨 Realm 的数组判断。

7.2 Number.isNaNNumber.isFinite

console.log(Number.isNaN(NaN)) // true
console.log(Number.isNaN('NaN')) // false
console.log(Number.isNaN({})) // false

console.log(Number.isFinite(123)) // true
console.log(Number.isFinite(Infinity)) // false
console.log(Number.isFinite('123')) // false:不会隐式转换

7.3 Object.hasOwn

如果目标是判断对象是否拥有自有属性,不要用类型判断 API:

const object = Object.create({ inherited: true })
object.own = true

console.log(Object.hasOwn(object, 'own')) // true
console.log(Object.hasOwn(object, 'inherited')) // false
console.log('inherited' in object) // true:包含原型链

八、Object.prototype.toString

借用 Object.prototype.toString 可以得到许多内建对象的标签:

const toString = Object.prototype.toString

console.log(toString.call(123)) // [object Number]
console.log(toString.call(123n)) // [object BigInt]
console.log(toString.call('hello')) // [object String]
console.log(toString.call(true)) // [object Boolean]
console.log(toString.call(undefined)) // [object Undefined]
console.log(toString.call(null)) // [object Null]
console.log(toString.call({})) // [object Object]
console.log(toString.call([])) // [object Array]
console.log(toString.call(function () {})) // [object Function]
console.log(toString.call(new Date())) // [object Date]
console.log(toString.call(/pattern/)) // [object RegExp]

可以封装成辅助函数:

function getTag(value) {
  return Object.prototype.toString.call(value).slice(8, -1)
}

console.log(getTag(Symbol('id'))) // Symbol
console.log(getTag(new Map())) // Map
console.log(getTag(new Set())) // Set

8.1 Symbol.toStringTag 可改变标签

const custom = {
  get [Symbol.toStringTag]() {
    return 'CustomValue'
  }
}

console.log(Object.prototype.toString.call(custom)) // [object CustomValue]

标签是可观察的显示信息,不是防伪品牌。读取标签可能触发 getter;在 Proxy 上还可能执行用户代码。因此 Object.prototype.toString 适合作为辅助诊断,不应作为安全边界或绝对类型证明。

九、鸭子类型(Duck Typing)

鸭子类型不是判断“它来自哪个构造器”,而是判断对象是否具备需要的能力:“如果它走起来像鸭子、叫起来像鸭子,就可以按鸭子使用”。

function canBeClosed(resource) {
  return resource !== null && typeof resource?.close === 'function'
}

function useResource(resource) {
  if (!canBeClosed(resource)) {
    throw new TypeError('resource 必须提供 close()')
  }

  try {
    return resource.read?.()
  } finally {
    resource.close()
  }
}

鸭子类型适合能力型 API、插件和跨 Realm thenable,但不要仅凭一个容易伪造的方法就授予敏感权限。

9.1 Promise/thenable 判断

value instanceof Promise 不能可靠识别跨 Realm Promise,也不能覆盖所有 thenable。若只需要等待一个值,直接使用 Promise.resolve(value)

function isThenable(value) {
  return value !== null &&
    (typeof value === 'object' || typeof value === 'function') &&
    typeof value.then === 'function'
}

console.log(isThenable(Promise.resolve())) // true
console.log(isThenable({ then() {} })) // true
console.log(isThenable({ catch() {} })) // false:catch 不是 thenable 的必要条件

“是否是原生 Promise”与“是否可被 Promise 处理”是不同问题,应根据 API 契约选择判断方式。

十、Symbol.toStringTag

Symbol.toStringTag 允许对象为 Object.prototype.toString 提供自定义标签:

class MyCollection {
  get [Symbol.toStringTag]() {
    return 'MyCollection'
  }
}

const collection = new MyCollection()
console.log(Object.prototype.toString.call(collection)) // [object MyCollection]

它适合调试、日志和开发者工具展示,不适合做安全类型校验。若需要识别自己定义的 class,可以使用私有字段品牌:

class Token {
  #brand = true

  static isToken(value) {
    try {
      return value instanceof Token && value.#brand === true
    } catch {
      return false
    }
  }
}

console.log(Token.isToken(new Token())) // true

私有字段不能被普通对象伪造,但跨 Realm 的对象仍需由跨 Realm 的协议或消息边界重新验证。

十一、直接比较与特殊值

有些“类型判断”其实只是判断特殊值:

function isNull(value) {
  return value === null
}

function isUndefined(value) {
  return value === undefined
}

function isBoolean(value) {
  return value === true || value === false
}

console.log(isNull(null)) // true
console.log(isUndefined(undefined)) // true
console.log(isBoolean(false)) // true

Object.is 可以处理 NaN-0 的特殊比较:

console.log(NaN === NaN) // false
console.log(Object.is(NaN, NaN)) // true
console.log(0 === -0) // true
console.log(Object.is(0, -0)) // false

原文提到 underscore 的 isNullisUndefinedisBoolean 等工具函数,它们的核心仍然是严格比较或标准 API。现代项目不需要为了 void 0 专门封装 undefined 判断。

11.1 void 0 的历史背景

console.log(void 0 === undefined) // true

早期浏览器中,顶层 undefined 可能被覆盖,因此代码曾使用 void 0 获得稳定的 undefined 值。现代 JavaScript 中 undefined 已是不可重新绑定的全局属性,普通业务代码直接使用 value === undefined 即可;void 0 可以作为历史兼容写法保留,但不再是必需方案。

十二、NaN 判断

12.1 全局 isNaN

全局 isNaN 会先进行数值转换:

console.log(isNaN(NaN)) // true
console.log(isNaN({})) // true:对象转换成 NaN
console.log(isNaN('123')) // false:字符串转换成 123

这在需要“转换后是否不是数字”时可能有用,但不适合严格判断输入是否是 NaN。

12.2 Number.isNaN

console.log(Number.isNaN(NaN)) // true
console.log(Number.isNaN({})) // false
console.log(Number.isNaN('NaN')) // false

推荐使用 Number.isNaN。如果必须支持非常旧的环境,可以使用:

function isNaNValue(value) {
  return typeof value === 'number' && value !== value
}

value !== value 只有 NaN 满足,但可读性通常不如 Number.isNaN

十三、indexOfincludes

数组的 indexOf 使用严格相等,找不到 NaN;includes 使用 SameValueZero,可以找到 NaN:

const values = [NaN]

console.log(values.indexOf(NaN)) // -1
console.log(values.includes(NaN)) // true

查找对象时二者都按引用比较,不会根据对象内容深度比较:

console.log([{ id: 1 }].includes({ id: 1 })) // false

十四、常用选择表

目标推荐方法主要边界
判断原始类型typeofnull 为 object,NaN 为 number,包含 bigint/symbol
判断 nullvalue === nullnull 是独立的原始值
判断数组Array.isArray(value)instanceof Array 更适合跨 Realm
判断 NaNNumber.isNaN(value)不做隐式转换
判断有限数字Number.isFinite(value)不做隐式转换
判断同 Realm class 实例value instanceof Class受原型、Proxy、Realm、hasInstance 影响
判断原型关系Object.getPrototypeOf / isPrototypeOf查询的是原型链,不是品牌
获取内建标签Object.prototype.toString.call(value)Symbol.toStringTag 可改写,可能执行 getter
判断能力显式检查方法/属性能力可能被伪造或有副作用
判断自有属性Object.hasOwn(value, key)不要用 in 替代
判断 -0、NaNObject.is=== 的零和 NaN 规则不同

十五、一个组合式类型辅助函数

如果项目需要统一输出类型标签,可以把规则写成有限且可审计的函数,不要宣称它能识别所有对象品牌:

function describeValue(value) {
  if (value === null) return 'null'

  const primitiveType = typeof value
  if (primitiveType !== 'object') return primitiveType

  if (Array.isArray(value)) return 'array'

  const tag = Object.prototype.toString.call(value)
  switch (tag) {
    case '[object Date]':
      return 'date'
    case '[object RegExp]':
      return 'regexp'
    case '[object Map]':
      return 'map'
    case '[object Set]':
      return 'set'
    default:
      return 'object'
  }
}

console.log(describeValue(null)) // null
console.log(describeValue(1n)) // bigint
console.log(describeValue([])) // array
console.log(describeValue(new Date())) // date

这个辅助函数仍会受到 Proxy、Symbol.toStringTag 和跨 Realm 细节影响;它适合日志和业务分支,不应作为安全验证的唯一依据。

十六、总结

  • typeof 适合原始类型和函数的快速判断,但要记住 null、NaN、bigint、symbol、TDZ 和 document.all
  • constructor 来自属性/原型查找,可以被覆盖,不适合作为可靠品牌;
  • instanceof 查询原型链,受到 Symbol.hasInstance、Proxy、原型修改和跨 Realm 影响;
  • isPrototypeOf 应按正确大小写书写,Object.getPrototypeOf 读取直接原型;
  • 数组使用 Array.isArray,NaN 使用 Number.isNaN,有限数字使用 Number.isFinite
  • Object.prototype.toString 是有用的辅助标签,但 Symbol.toStringTag 可以改变显示结果;
  • 鸭子类型检查能力,不等于检查构造器身份;
  • Object.is 适合需要区分 NaN 或 -0 的比较;
  • 判断自有属性使用 Object.hasOwn,包含原型链使用 in
  • 类型判断应服务于明确的业务契约,不能把某个字符串标签当作绝对安全证明。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS