JS中的类型判断也是咱的老朋友了,如果要你说两句,那便是啥玩意typeof
、instanceof
的,张口就来,但你真的掌握了每一种类型判断,并熟知其原理吗?就比如说Object.prototype.toString.call()
,当面试官问其中的 call
有什么作用时,咱们又该如何游说呢。
typeof原理
所有的类型判断里当属typeof
最为经典,其最为出名之处就是这玩意不顶用,typeof
居然会将null
判定为object
,但这到底是什么原因?实际上,这就是JS这门编程语言的官方团队造出来的一个bug。
let s = '123'
let n = 123
let f = true
let u = undefined
let nu = null
let sy = Symbol(123)
let big = 1234n
let obj = {}
let arr = []
let fn = function(){}
let date = new Date();
console.log(typeof(s)); // string
console.log(typeof(n)); // number
console.log(typeof(f)) // boolean
console.log(typeof(u)) // undifined
console.log(typeof(sy)) // symbol
console.log(typeof(big)) // bigint
console.log(typeof(nu)); //将null判断为object
//以下为引用类型,除了function,一律被判断为object
console.log(typeof(obj)) //object
console.log(typeof(arr)) //object
console.log(typeof(data)) //object
// 所有引用类型只能判断function
console.log(typeof(fn)) //function
//
原因就是:typeof
会把所有传进去的值都转成二进制,而当年JS制定的规则便是:原始类型的被转为二进制的前面三个值绝对不为零,而typeof
会把前三位为零的类型全部认定为对象,null
这个类型又是JS语言后来引入的。遵循其他语言的原则,JS语言将null
的二进制值定为一长串的0,因此typeof
在判断时会将其认定为object
。
因此,面试官一般不会询问判断方法 typeof
的原理。
手写一个instanceof
相较于typeof
,instanceof
有其优越之处,也有其不足之处,比如,instanceof
只能判断引用类型,并且是通过原型链查找来判断类型。
let s = '123'
let n = 123
let f = true
let u = undefined
let nu = null
let sy = Symbol(123)
let big = 1234n
let obj = {}
let arr = []
let fn = function(){}
let date = new Date();
console.log(s instanceof String); // false instanceof 不能判断原始类型
// 以下为引用类型
console.log(obj instanceof Object) ; // true
console.log(arr instanceof Object); // true
console.log(fn instanceof Function); // true
console.log(date instanceof Date); // true
首先要知道每个类型的原型都不相同。
instanceof
的原理具体来说就是instanceof
会顺着原型链查找出其继承的原型,看这个原型到底是属于String
、Number
、Boolean
等具体类型的哪一种,如果被判断的类型与我们期望的类型的原型相同,输出true,否则输出false。
关于原型,原型链的定义可以参考这篇文章:juejin.cn/post/737503…
以下则为手写myinstanceof
代码:
function myinstanceof(L, R){
while(L !== null){
if(L.__proto__ === R.prototype){
return true;
}
L = L.__proto__
}
return false
}
console.log(myinstanceof([], Array)) // true
令人奇怪的是,为什么原型的判断中牵扯到while
?熟知原型链的友友应该不难理解,正如之前所说,instanceof
是通过原型链查找来判断类型,那岂不知显示原型生隐式原型,如此往复,子子孙孙无穷尽也,正如下代码,如果用舍弃 while
转而用if
进行进行判断,仅仅是三代原型的传递,就要用到三次if
判断,可想而知原型链一长,代码都打不过来的光景。
以下为不正确的myinstanceof
手写代码:
function B(){}
let b = new B();
A.prototype = b;
function A(){}
let a = new A();
function myinstanceof(L,R){
if(L.__proto__ === R.prototype){
return true;
} else {
if(L.__proto__.__proto__ === R.prototype) {
return true
} else {
if(L.__proto__.__proto__.__proto__ === R.prototype)
return true
}
}
}
console.log(myinstanceof(a, Object)) // true
最完美的判断方式:Object.prototype.toString.call()
类型判断中最为完美的毋庸置疑,当属 Object.prototype.toString.call()
,这是一种属于object
原型上的方法。它能做到正确的判断出每一种类型。
let s = '123'
let n = 123
let f = true
let u = undefined
let nu = null
let sy = Symbol(123)
let big = 1234n
let obj = {}
let arr = []
let fn = function(){}
let date = new Date();
console.log(Object.prototype.toString.call(s)); // [object String]
console.log(Object.prototype.toString.call(n)); // [object Number]
console.log(Object.prototype.toString.call(f)); // [object Boolean]
console.log(Object.prototype.toString.call(u)); // [object Undefined]
console.log(Object.prototype.toString.call(nu)); // [object Null]
console.log(Object.prototype.toString.call(obj)); // [object Object]
console.log(Object.prototype.toString.call(arr)); // [object Array]
console.log(Object.prototype.toString.call(fn)); // [object Function]
console.log(Object.prototype.toString.call(date)); // [object Date]
这种判断方法的完整代码为 Object.prototype.toString.call()
,关于它的原理,我们来看看官网是怎么对Object.prototype.toString()
进行解释的:
翻译成人话就是:
-
如果你传进来的值为
undefined
的话,直接返回一个[object Undefined]。 -
如果你传进来的值为
null
的话,直接返回一个[object Null]。 -
如果你既不是
undefined
又不是null
的话,JS将调用ToObject
方法,将O
作为ToObject(this)
的执行结果。
ToObject
的执行机理简单来说就是,传进来一个boolean
类型会创建一个boolean
包装类对象,传进来Number
会创建一个Number
字面量,传进来一个String
会创建一个字符串字面量,传进来一个对象就会创建这个对象。总而言之,任何传进去的任何值都会转换为对象。
-
定义一个
class
作为内部属性[[class]]
的值,用于承接传进来的值。 -
返回由
"["
object
和class
和"]"
组成的字符串。
现在我们知道 Object.prototype.toString()
的原理了,但是 Object.prototype.toString.call()
后面加的这个call
又有什么作用呢?让我们先试着输出一下:
可以看到令人啼笑皆非的一幕发生了,Object.prototype.toString()
将 123 判断为object
,而Object.prototype.toString.call()
则输出了正确的判断,这又是什么原理?
这正是Object.prototype.toString()
执行过程中调用ToObject()
的结果,上文提到,ToObject()
会把你传进来的值都创建为相应的类型,123 传进来被创建为String
,而字符串类型在V8眼里可不就是对象嘛。
需要注意的是所有原始类型都会被V8以对象形式创建,关于这点可以参考这篇文章juejin.cn/post/737397…
现在我们来说明 call
在此的作用,我们都知道.call(obj)
的作用是将 .call
之前的函数中的 this
指向 obj,但也可以说是把 .call
之前的函数方法借给 obj 去使用,因此在Object.prototype.toString.call()
中,toString()
是这样被调用的:
Object.prototype.toString.call(obj)
obj.toString()
就像上文对123的判断,当没有call
时toString
是在被123实例对象调用,而当添加上call
后,toString
就变成是被123的原型所调用。因此,call
确保了类型判断是被原型在调用,从而能输出正确的值。
Array.isArray()
最后一个要讲的就是Array.isArray()
,这是一个隶属于 Array
构造函数的静态方法,遗憾的是,它只能判断数组。
let arr = []
let s = '123'
console.log(Array.isArray(arr)); // true
console.log(Array.isArray(s)); // false
总结:
typeof
- 可以判断除
null
之外的所有原始类型。 - 除了
function
其他所有的引用类型都会被判断成object。 typeof
是通过将值转换为二进制后判断其二进制前三位是否为0,是则为object
。
instanceof
- 只能判断引用类型。
- 通过原型链查找来判断类型。
Object.prototype.toString()
- 一种Object原型上的方法。
Array.isArray()
- 一个隶属于
Array
构造函数的静态方法。
以上便是 typeof、instanceof、Object.prototype.toString.call()、Array.isArray() 这四种类型判断。虽然我说其中Object.prototype.toString()
的判断最为完美全面,但在实际应用之中它们各有优劣之处,应用场景也各有不同。