创见博客
JS 数据类型有哪些,以及如何判断数据类型
七崽爱吃小饼干2026/10/07阅读 0

先看一段输出,你就知道数据类型判断为什么总被人反复问:

js
typeof null          // "object"   —— 明明是空值
typeof []            // "object"   —— 分不清数组
typeof NaN           // "number"   —— 它压根不是数字
typeof function(){}  // "function" —— 唯一被特殊对待的引用类型
new Date() instanceof Date   // true
new Date() instanceof Object // true,链上有 Object

一句话概括:JavaScript 的类型系统里有几个历史遗留的坑,不同判断手段各管一段,没有一个是万能的。这篇文章把类型先分清楚,再把 typeof、instanceof、constructor、Object.prototype.toString 逐一对号入座。

一、JS 到底有几种数据类型

ECMAScript 语言层面把类型分成两大类:原始类型(Primitive) 和 引用类型(Object)。

原始类型一共 7 种,数据直接存在栈里,按值访问:

类型说明例子
Undefined声明未赋值let a
Null空引用,历史遗留null
Boolean布尔true / false
Number双精度浮点1 / 0.1 / NaN
String字符串"abc"
Symbol唯一标识符,ES6Symbol("id")
BigInt任意精度整数,ES202010n

引用类型则是一大类,统称 Object,常见的有:

  • 普通对象 {}
  • 数组 []
  • 函数 function(){}
  • 日期 new Date()
  • 正则 /abc/
  • 以及 Map、Set、WeakMap、Promise 等等

两者最本质的区别在复制行为:

js
// 原始类型:按值传递,互不影响
let a = 1
let b = a
b = 2
console.log(a) // 1

// 引用类型:变量存的是地址,改的是同一块内存
let o1 = { count: 1 }
let o2 = o1
o2.count = 2
console.log(o1.count) // 2

二、typeof:最常用,但有三个坑

typeof 是最顺手的运算符,返回一个字符串。它对原始类型几乎都能识别:

js
typeof undefined   // "undefined"
typeof null        // "object"   ❌ 坑 1
typeof true        // "boolean"
typeof 1           // "number"
typeof "abc"       // "string"
typeof Symbol()    // "symbol"
typeof 10n         // "bigint"

对引用类型,它只能区分出函数,其余一律是 "object":

js
typeof {}          // "object"
typeof []          // "object"   ❌ 坑 2:数组识别不了
typeof null        // "object"
typeof function(){} // "function"

另外 typeof 对未声明的变量不会抛错,这是它一个隐藏优点:

js
typeof notDefined // "undefined",不会报 ReferenceError

三个坑总结:

  1. typeof null === "object":这是第一版 JS 用低位标记类型留下的 bug,值全 0 被视为对象指针,修复会破坏太多旧代码,于是被保留至今。
  2. typeof [] === "object":数组、日期、正则都无法区分。
  3. typeof NaN === "number":NaN 全称 Not a Number,但它确实是 Number 类型的一个值。

所以 typeof 的正确用法是:判断原始类型和函数,判断 null 要单独写 value === null。

三、instanceof:靠原型链,只适合引用类型

instanceof 检查构造函数的 prototype 是否出现在对象的原型链上:

js
[] instanceof Array          // true
[] instanceof Object         // true,Array 继承自 Object
new Date() instanceof Date   // true
new Date() instanceof Object // true

它能很好地解决数组、日期、正则的区分问题:

js
const isArray = (v) => v instanceof Array
const isDate = (v) => v instanceof Date

但有两个明显限制:

  • 对原始类型几乎失效,因为原始值不是对象,没有原型链:
js
1 instanceof Number       // false
"abc" instanceof String   // false
  • 跨 iframe / 跨 realm 会失灵,不同全局环境有各自的构造函数:
js
// 页面 A 的数组拿到页面 B 里判断
iframeArray instanceof Array  // 可能为 false

还有一个要小心的点:instanceof 可以被 Symbol.hasInstance 自定义,也能通过改原型欺骗:

js
const fake = {}
Object.setPrototypeOf(fake, Array.prototype)
fake instanceof Array // true,但 fake 不是数组

四、constructor:能读,但不可靠

每个对象通过原型链能拿到 constructor 属性,指向创建它的构造函数:

js
(1).constructor === Number        // true
"abc".constructor === String      // true
[].constructor === Array          // true
({}).constructor === Object       // true

看起来不错,但它有两个致命问题:

js
// 1. null 和 undefined 没有 constructor,直接报错
null.constructor // TypeError

// 2. constructor 可以被随意改写
function Foo() {}
Foo.prototype = {}          // 原型被替换,constructor 丢了
new Foo().constructor === Foo // false

所以 constructor 只能作为辅助,不能当作类型判断的主手段。

五、Object.prototype.toString:最通用的一招

这是社区公认最稳的判断方式。调用它时,内部会读取对象的 [[Class]] / Symbol.toStringTag,返回 "[object Xxx]":

js
Object.prototype.toString.call(undefined)  // "[object Undefined]"
Object.prototype.toString.call(null)       // "[object Null]"
Object.prototype.toString.call(1)          // "[object Number]"
Object.prototype.toString.call("abc")      // "[object String]"
Object.prototype.toString.call(true)       // "[object Boolean]"
Object.prototype.toString.call(Symbol())   // "[object Symbol]"
Object.prototype.toString.call(10n)        // "[object BigInt]"
Object.prototype.toString.call([])         // "[object Array]"
Object.prototype.toString.call(new Date()) // "[object Date]"
Object.prototype.toString.call(/abc/)      // "[object RegExp]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call(new Map())  // "[object Map]"

它几乎能区分所有内置类型,而且不受原型链篡改影响。缺点是两个:

  • 需要 .call() / .apply(),因为直接 obj.toString() 可能被对象自己重写。
  • 对自定义类的实例,返回的还是 "[object Object]",无法区分业务对象。

六、几种针对性的快捷方法

有些场景有更语义化的 API,优先用它们:

js
Array.isArray([])        // true,官方推荐的数组判断
Number.isNaN(NaN)        // true,只对真正的 NaN 返回 true
Number.isInteger(1.0)    // true,判断整数
Object.is(NaN, NaN)      // true,比 === 更严格(能区分 +0 / -0)

Array.isArray 能正确处理跨 realm 的数组,比 instanceof Array 更可靠。

七、手写一个通用的 type 函数

把上面几招组合起来,封装一个覆盖 99% 场景的工具:

js
function getType(value) {
  // 优先用 toString 拿到内置类型
  const raw = Object.prototype.toString.call(value)
  const type = raw.slice(8, -1).toLowerCase() // "[object Array]" -> "array"

  // 修正 null 和 NaN 这两个特殊值
  if (value === null) return 'null'
  if (type === 'number' && Number.isNaN(value)) return 'nan'

  return type
}

getType(undefined)        // "undefined"
getType(null)             // "null"
getType(1)                // "number"
getType(NaN)              // "nan"
getType("abc")            // "string"
getType(true)             // "boolean"
getType(Symbol())         // "symbol"
getType(10n)              // "bigint"
getType([])               // "array"
getType({})               // "object"
getType(new Date())       // "date"
getType(/abc/)            // "regexp"
getType(function(){})     // "function"

如果还想支持 Map、Set、Promise,直接在上面结果上判断即可,toString 已经返回了对应的标签。

八、一张表选对工具

判断目标推荐方式备注
原始类型typeof vnull 例外,需 v === null
函数typeof v === "function"简单直接
数组Array.isArray(v)支持跨 realm
日期 / 正则 / Map / SetObject.prototype.toString.call(v)最通用
NaNNumber.isNaN(v)不要用全局 isNaN
整数Number.isInteger(v)—
是某个类的实例v instanceof Foo仅引用类型,注意跨 realm
自定义对象自定义 Symbol.toStringTagtoString 会读取它

小结

  • JS 有 7 种原始类型和一类引用类型,复制行为完全不同。
  • typeof 判原始类型和函数,但 null 会误报 "object",数组分不出。
  • instanceof 基于原型链,只适合引用类型,跨 iframe 会失效。
  • constructor 可被改写,null 上会报错,别单独用。
  • Object.prototype.toString.call() 是通用解法,配合特殊值修正可封装出稳定的 getType。
  • 日常优先用针对性 API:Array.isArray、Number.isNaN、Number.isInteger。

记住一句话:没有万能钥匙,先想清楚你要判断的是哪一层——原始值、内置对象,还是某个自定义类的实例,再选对应的工具。

评论
0/100