JavaScript 四种数据类型判断方法
JavaScript 属于弱类型(动态类型)语言,不需要提前声明变量类型,运行时自动确定类型。同一个变量可以先后存储多种不同类型的数据:
let a = 42
a = "bar"
a = true
a = {}
ECMAScript 标准一共定义 8 种数据类型:
7 种基本(原始)类型:Boolean、Null、Undefined、Number、BigInt、String、Symbol
基本类型又叫简单类型。教学上常说它们放在栈上、按值访问;引擎实现里对象在堆上,部分原始值也会落在堆上。除 null、undefined 外都有对应的包装对象。
1 种引用类型:Object
Object 是唯一复杂类型。Function、Array、RegExp、Date 等本质都归属于 Object。引用数据存放在堆内存,变量保存的是指向堆内存的指针,按引用访问。
除 Object 外,所有原始类型的值本身不可变。例如字符串不能原地修改某个字符,操作只会返回新字符串,原始字符串保持不变。
由于 JavaScript 是弱类型,开发中经常需要判断变量实际的数据类型。语言提供了多种判断手段,但没有任何一种可以覆盖全部场景,每种方案都有各自的适用范围与缺陷。
let objT = {
str: '',
num: 1,
bool: true,
undef: undefined,
nul: null,
symbol: Symbol(),
bigInt: BigInt(10),
fun: new Function(),
arr: [],
numN: new Number(),
boolN: new Boolean(),
strN: new String(),
date: new Date(),
reg: new RegExp(),
}
typeof
typeof variable
typeof 运算符返回全小写字符串,代表操作数的类型。
优点:
- 使用简单,语法简洁;
- 可以正确识别:
string、number、boolean、symbol、bigint、undefined、function; - 对未声明的标识符执行
typeof不会抛错,返回'undefined'。
缺点:
typeof null === 'object'(历史遗留,规范保留)typeof null // 'object'JavaScript 最初实现里,对象用类型标签 0 标记,
null在底层是空指针(多数平台为 0x00),因此返回'object'。曾经有提案改成'null',最终被拒绝。除
function以外,其余引用类型(数组、Date、RegExp等)全部返回'object',无法区分。typeof [] // 'object' typeof new Date() // 'object' typeof /abc/ // 'object'包装对象实例(
new Number、new String、new Boolean)识别为'object',不是对应的原始类型。let str = new String('abc') typeof str // 'object'部分老浏览器正则会识别为
function(历史兼容问题,现代浏览器已修复)。特殊宿主对象:
document.all,typeof document.all === 'undefined',属于标准的故意例外行为。暂时性死区(TDZ)里,对已进入作用域但尚未初始化的
let、const执行typeof,会抛出ReferenceError。未声明的标识符才返回'undefined'。for (let k in objT) { console.log(typeof objT[k]) } // str → string // num → number // bool → boolean // undef → undefined // nul → object // symbol → symbol // bigInt → bigint // fun → function // arr → object // numN → object // boolN → object // strN → object // date → object // reg → object
适用场景:判断原始类型(除 null)、判断函数;不适合区分数组、日期、正则、null。
instanceof
object instanceof constructor
检测构造函数的 prototype 是否出现在实例对象的原型链上。返回布尔值。
优点:
- 可以区分自定义类的实例;
- 可以识别部分内置引用类型:
Array、Date、RegExp、Function。
缺点:
不能判断原始类型,原始值直接返回
false。null instanceof Object、undefined instanceof Object同样返回false,不会抛错。右侧不是对象(或不能用作构造函数)时才会抛错。'abc' instanceof String // false 123 instanceof Number // false true instanceof Boolean // false null instanceof Object // false undefined instanceof Object // false普通对象都继承
Object,因此xxx instanceof Object常常为true,无法单靠它精确区分真实类型。Object.create(null)没有原型,instanceof Object为false。[] instanceof Array // true [] instanceof Object // true Object.create(null) instanceof Object // false原型链可以被人为篡改,判断结果不可靠:修改构造函数
prototype,或者修改对象[[Prototype]](如__proto__),会改变结果。对象还可以自定义Symbol.hasInstance。function A() {} const a = new A() a instanceof A // true A.prototype = null a instanceof A // TypeError:prototype 不是对象跨 iframe、跨窗口环境失效:不同 iframe 拥有独立的全局构造函数。
document.body.appendChild(document.createElement('iframe')) const xArray = window.frames[0].Array const arr = new xArray(1,2,3) arr instanceof Array // false arr instanceof xArray // true
ES5 专门提供 Array.isArray() 解决数组跨窗口判断的问题。
适用场景:判断自定义类的实例对象;仅用于引用类型,不适合原始类型。
constructor
constructor 是原型上的属性,指向创建该实例的构造函数。判断写法:
obj.constructor === Constructor
优点:
原始类型在访问
.constructor时自动包装为包装对象,可以拿到对应构造函数。(10).constructor === Number // true 'hello'.constructor === String // true true.constructor === Boolean // true可以识别内置对象实例。
缺点:
null、undefined没有对象,访问.constructor直接抛出异常。Object.create(null)没有constructor,同样会抛错。// objT.nul.constructor → Uncaught TypeErrorconstructor属性可被人为改写,判断不安全:function A(){} const a = new A() a.constructor = Date a.constructor === A // false跨 iframe、跨窗口同样失效,和
instanceof问题一致。document.body.appendChild(document.createElement('iframe')) const xArray = window.frames[0].Array const arr = new xArray(1,2,3) arr.constructor === Array // false arr.constructor === xArray // true
适用场景:简单业务,且保证不会篡改 constructor;不推荐做严谨类型校验。
Object.prototype.toString.call()
Object.prototype.toString.call(variable)
该方法读取对象的内置品牌(ES5 对应内部属性 [[Class]];现行规范还看 @@toStringTag),返回格式为 [object Xxx] 的字符串。
Array、Date、RegExp 等内置对象重写了自身的 toString,因此不要直接调用 xxx.toString(),必须使用原型方法再 call 改变 this。
优点:
JavaScript 内置类型(原始类型、内置对象)几乎全部可以精准识别:
Object.prototype.toString.call('') // "[object String]" Object.prototype.toString.call(1) // "[object Number]" Object.prototype.toString.call(true) // "[object Boolean]" Object.prototype.toString.call(null) // "[object Null]" Object.prototype.toString.call(undefined) // "[object Undefined]" Object.prototype.toString.call(Symbol()) // "[object Symbol]" Object.prototype.toString.call(BigInt(1)) // "[object BigInt]" Object.prototype.toString.call([]) // "[object Array]" Object.prototype.toString.call({}) // "[object Object]" Object.prototype.toString.call(new Date()) // "[object Date]" Object.prototype.toString.call(/\d/) // "[object RegExp]" Object.prototype.toString.call(function(){}) // "[object Function]"返回纯字符串,不受跨 iframe 环境影响;
内置对象的品牌(数组、日期等)不会因为改原型就变成别的标签。对象仍可以自定义
Symbol.toStringTag,从而改变toString的返回值。
缺点:
默认无法区分自定义类实例,未设置
Symbol.toStringTag时统一返回[object Object]。class Person {} const p = new Person() Object.prototype.toString.call(p) // "[object Object]" class Animal { get [Symbol.toStringTag]() { return 'Animal' } } Object.prototype.toString.call(new Animal()) // "[object Animal]"
适用场景:JavaScript 所有内置标准类型(原始、内置引用)的严谨判断;自定义类若未设置 Symbol.toStringTag,不能靠它区分。
什么是内部属性 [[Class]] 与 @@toStringTag
ES5 里,Object.prototype.toString 读取内部属性 [[Class]],开发者无法直接访问。现行规范改为优先使用 @@toStringTag(Symbol.toStringTag),内置对象仍有固定品牌。
内置对象的标签和其内置构造函数一一对应;null、undefined 也有特殊结果([object Null]、[object Undefined])。
用户自定义类的实例,若未定义 Symbol.toStringTag,结果仍是 [object Object],所以默认无法用它区分自定义类。
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(/a/i ) // '[object RegExp]'
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call("abc") // '[object String]'
Object.prototype.toString.call(1234) // '[object Number]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
总结
| 判断方式 | 可识别原始类型 | 识别 null | 识别内置引用类型 | 识别自定义类 | 风险、缺陷 |
|---|---|---|---|---|---|
| typeof | ✅大部分 | ❌ | ❌(仅 function) | ❌ | null 历史行为、TDZ 抛错,无法区分数组、日期 |
| instanceof | ❌ | ❌ | ✅ | ✅ | 原型可篡改、跨 iframe 失效、不能原始类型 |
| constructor | ✅ | ❌ | ✅ | ✅ | 属性可被改写、跨 iframe 失效 |
| Object.prototype.toString.call | ✅ | ✅ | ✅ | ❌ | 默认无法识别自定义类实例 |
- 判断内置标准类型(原始、
Array、Date、RegExp等):优先用Object.prototype.toString.call(),是内置类型最可靠方案; - 判断自定义 class、构造函数实例:使用
instanceof; - 简单判断原始类型(排除
null)、判断是否为函数:可以用typeof; - 不推荐业务中依靠
constructor做类型校验,属性可被篡改,安全性差。
instanceof、constructor 都受原型链篡改、跨窗口 iframe 环境影响,做公共库、工具函数时要格外小心。