一、引言
在 JavaScript 开发中,我们经常需要判断一个变量的类型:是字符串还是数字?是数组还是普通对象?是
null还是undefined?不同类型的判断需求,对应着不同的方法 ——typeof、instanceof、Object.prototype.toString等。它们各有优缺点和适用场景,本文将从底层原理出发,带大家搞明白三种常用类型判断方法的工作原理,让你在实际开发中做到“因地制宜”。
二、类型回顾
我们如果要进行类型判断,首先我们一定要清楚都有哪些类型:
- 原始类型:string, number, boolaen, null, undefined, bigint, Symbol
- 引用类型: Array, function, object, Date
具体的详细类型介绍与各种小tips可以看我往期的文章: juejin.cn/post/763992…
三、typeof
我们先用typeof对所有的常用类型进行打印输出console.log(typeof(s))(s依次替换成num,f,u等等),得到的结果是这个样子的:
let s = 'yunbao' //string
let num = 463 //number
let f = true //boolean
let u = undefined //undefined
let n = null //object--------这里出问题了!
let sy = Symbol(1) //symbol
let big = 12345678n //bigint
let arr = [] //object--------这里也出问题了!
let obj = {} //object--------存疑,因为我们不知道是否arr出的问题一致
let fn = function () { } //function
于是我们查阅JS官方文档可知:
typeof是通过将值转为二进制,来判断类型的,二进制前三位是 0 的统一被认为是引用类型,在计算机中,所有的引用类型被转为二进制前三位都是 0(除了函数),而 null 转为二进制是一整串 0 ,因此会出现代码中的那三个特殊情况。
所以:
typeof可以准确的判断除了 null 之外的所有的原始类型- 所有的
引用类型在 typeof 眼里都是object,除了函数
四、instanceof
因为最简单的typeof分辨不出null,Array和object,我们便找到了第二种方法——instanceof
这种方法比较“呆傻”,只能“你问我答”式,还是用代码一目了然一下,我们用console.log(s instanceof String)依次打印输出:
let s = 'yunbao' //false
let num = 463 //false
let f = true //false
let u = undefined //-------这里报错(undefined is not defined)
let n = null //-------这里也报错(Null is not defined)
let sy = Symbol(1) //false
let big = 12345678n //false
let arr = [] //true
let obj = {} //true
let fn = function () { } //true
由此可知:
instanceof只能判断引用类型,无法判断原始类型
实际上, instanceof是通过隐式原型链来向上查找 ** 是否隶属于 ** 这个类型的:
- 如果arr._ _ proto _ _ === Array.prototype ------- 返回 true
- 如果不等于,便继续沿着当前原型的原型链向上查找(也就是取
__proto__.__proto__)Array.prototype._ _ proto _ _ === Object.prototype
- 直到向上查找到null,Object.prototype._ _ proto _ _ === null
此时还不相等才会返回false
五、Object.prototype.toString.call()
一步一步到这里我们终于迎来了一个万能的方法来判断数据类型,Object.prototype.toString.call()用其精妙的底层逻辑,巧用this判断所有数据类型。
在聊Object.prototype.toString.call()之前,我们需要普及官方的[[Class]]的相关知识:“es5.github.io/#x15.2.4.2” 这个JS官方文档指出每一个内置对象都有一个内部的 [[Class]] 属性,它的值是一个字符串,用于区分对象的种类,比如 "Array"、"Date"、"Number"、"String" 等。
Object.prototype.toString()的执行步骤
1. 如果 this 值为 undefined,则返回 "[object Undefined]"。
2. 如果此值为 null,则返回 "[object Null]"。
3. 否则,我们令O为 调用 ToObject 并将 this 值作为参数传递所得到的结果。
// const O = ToObject(this) 这里调用方法不变,this一直指向Object.prototype
// 因此 O 永远都是 Object
4. 令class为 O的[[Class]]内部属性的值。
// const class = O.[[calss]]
5. 返回将三个字符串 "[object "、class 和 "]" 拼接后得到的字符串值
此时我们在后面加上.call用来强行扭转this的值指向括号里的内容,使用Object.prototype.toString.call()便可以获取到括号里的参数的[[class]]类型,并且把类型赋值给O,最后再用O.[[calss]]将其调用出来,因此我们便可以正确得到该输入参数的明确类型!
六、扩展与总结
typeof虽然方便,但判断null会失真instanceof能区分引用类型却无法处理原始值Object.prototype.toString.call()什么都可以判断但稍微有些繁琐
因此我们要根据代码所需,选择最合适的判断方法,这样才能提升整段代码的优雅性与健硕性>_<!