引言
不少前端小伙伴写 JS 时总被数据类型坑:null被 typeof 错判成对象、数组识别为 Object,原生数值用 instanceof 判断屡屡出错,面试还总被深挖底层原理。本文细数 JS 全部数据类型,详解三种常用校验方式的优缺点与底层逻辑,搭配实操案例帮你按需选型,轻松避开类型判断各类 bug。
1. JS有哪些类型
基本类型:string、 number、 boolean、 null、 undefined、 symbol、 bigint。
引用类型:function、 array、 object、 date
想了解更多数据类型的知识点,可以看我往期的文章: 前端基础必看|JS数据类型全家桶
2. typeof 方法
内容:
- 可以准确的判断除了 null 之外的所有的原始类型
- 所有的引用类型在 typeof 眼里都是 object,除了函数
关于 typeof 方法的使用,用一段代码和你讲清楚:
let num = 123
let n = null
let u = undefined
let s = '12112'
let b = false
let sy = Symbol(1)
let bint = 123456n
let arr = []
let obj = {}
let fn = function(){}
console.log('number:' + typeof num)
console.log('null:' + typeof n) //object
console.log('undefined:' + typeof u)
console.log('string:' + typeof s)
console.log('boolean:' + typeof b)
console.log('symbol:' + typeof sy)
console.log('bigint:' + typeof bint)
console.log('object:' + typeof obj)
console.log('array:' + typeof arr) //object
console.log('function:' + typeof fn)
运行结果如下:
运行结果中,有两个类型的判断并不符合预期:null 和 array 都会被识别为 object。很多人都会对此感到疑惑,这究竟是为什么呢?想要弄清楚背后的原因,我们必须先了解 typeof 的底层执行原理:typeof 是通过将数据转换为二进制来判断类型的。在二进制判断规则中,只要数据的二进制前三位均为 0 ,就会被判定为引用类型;而所有引用类型转换为二进制后,前三位都是 0(函数除外),null 对应的二进制则是全 0。
3. instanceof 方法
内容:
- 只能判断引用类型,无法判断原始类型
- 它是通过隐式原型链来查找 x 是否隶属于 X 这个类型
接下来我们通过实际代码,直观演示 instanceof 方法的具体使用:
let num = 123
let n = null
let u = undefined
let s = 'new123'
let b = false
let sy = Symbol(1)
let bint = 123456n
let arr = []
let obj = {}
let fn = function(){}
console.log(arr instanceof Array) //true
console.log(arr instanceof Object) //true
console.log(obj instanceof Object) //true
console.log(fn instanceof Function) //true
console.log(s instanceof String) //false
console.log(num instanceof Number) //false
console.log(b instanceof Boolean) //false
console.log(bint instanceof BigInt); //false
console.log(sy instanceof Symbol); //false
运行结果如下:
不难发现,instanceof 仅适用于引用类型校验。那它又是依托隐式原型链,如何判断实例 x 是否归属于构造函数 X?下面结合源码拆解说明:
function myInstanceof(l, r) {
if (typeof l !== 'object' && typeof l !== 'function' || l == null) {
return false
}
while(l !== null) {
if (l.__proto__ === r.prototype) {
return true
}
l = l.__proto__
}
}
-
基础类型直接不匹配:
string/number/boolean等基础类型没有原型链,直接返回false; -
沿原型链向上查找:通过对象的
__proto__逐层往上遍历原型链; -
匹配规则:如果某一层原型
===构造函数的prototype,则返回true; -
查找到顶端结束:遍历到原型链终点
null仍未匹配,返回false。
4.Object.prototype.toString.call() 方法
Object.prototype.toString.call() 是 JavaScript 中最精准、最通用的类型判断方法,能完美区分所有数据类型
在聊 Object.prototype.toString.call() 方法前我们先了解一下什么是Object.prototype.toString() 方法。
官方介绍是这样的:
可能有点懵,我来给你解释一下:
- 如果 this 值为 undefined,则返回 "[object Undefined]"。
- 如果此值为 null,则返回 "[object Null]"。
- 令 O 为调用 ToObject 并将 this 值作为参数传递所得到的结果。
// const O = ToObject(this)
// O 永远都是 Object - 令class为O的[[Class]]内部属性的值。 // const class = O.[[calss]]
- 返回将三个字符串 "[object "、class 和 "]" 拼接后得到的字符串值。
所以Object.prototype.toString()方法是不能完成类型判断的,无论什么类型传进去都是Object。
Object.prototype.toString 是 JS 底层用于获取数据原生类型的方法,但它仅依赖 this 指向识别类型,直接调用时 this 为 Object.prototype,只能得到 [object Object];Object.prototype.toString.call() 则通过 call 强行修改 this 为待检测的值,让方法读取该值的真实内置类型,成为 JS 中最通用、最精准的类型判断方式。
通过示例代码演示 Object.prototype.toString.call() 的使用逻辑:
let s = 'hello'
let num = 123
let f = true
let u = undefined
let n = null
let sy = Symbol(1)
let big = 12343242n
let arr = []
let obj = {}
let fn = function(){}
console.log(Object.prototype.toString.call(s));
console.log(Object.prototype.toString.call(num));
console.log(Object.prototype.toString.call(arr).slice(8, -1));
console.log(Object.prototype.toString.call(fn).slice(8, -1)); // '[object Function]'
运行结果如下:
扩展知识 Array.isArray()
JS 专门给数组准备了快捷判断的方法
Array.isArray(),想查是不是数组直接丢参数就行,写法简单好用;但它只管数组,别的数据类型识别不了,查数字、日期、null 还得靠Object.prototype.toString.call()。
5.总结
typeof是 “快餐”:快但不精准,适合原始类型(除 null)和函数的快速判断;instanceof是 “族谱查档”:只认引用类型,靠原型链判断,适合自定义类的继承判断Object.prototype.toString。call()是 “CT 扫描”:精准识别所有类型,通用场景首选;Array.isArray()是 “数组专属 VIP”:判断数组比instanceof靠谱 10 倍。
记住这些,以后写类型判断再也不会被面试官问倒,也不会在开发中踩各种奇葩坑了!💪