三种类型判断的区别和原理解析(typeof/instanceof/Object.prototype.toString)
tip:因为本文偏向于底层,希望您已经掌握原型和原型链,new关键字的作用。并且对二进制有基本概念
我们最常见的类型判断方式有三种,typeof和instanceof还有Object.prototype.toString。
javascript中有两个类型大纲(基本数据类型和引用数据类型)。
基本数据类型包括有:Number,String,Boolean,null,Undefined,Symbol(ES6)等
引用数据类型包括有:Obejct,Array,Function,Map(ES6),Set(ES6)等
typeof(判断基本数据类型)
typeof只能用于判断基本数据类型(null除外)和函数。
在至今为止的所有基本数据类型中,只有null会被判断为"obejct",其余都可以准确判断。而在所有的引用数据类型中,只有Function会被准确判断,其余均会被判断为object。
如图👇
为什么null会被判断为object?
不论我们在编译器中输入的是什么代码,这些代码在最后最后会被编译成二进制数据。而在变量生成的组二进制数据中,系统通过这组二进制的 前三位(从右往左) 来判断当前数据的类型是哪一种。
javascript类型判断规则👇
- 000:对象
- 1:整数
- 010:浮点数
- 100:字符串
- 110:布尔
而null,因为不代表任何数值。他在javascript中最初的设计模式的表现形式是:0000000000000000
注意到了吗,null的前三位,也是000。
然后javascript进行了以下判断👇(以下是早期的javascript源码)
//1.判断是不是undefined
if (JSVAL_IS_VOID(v)) {
type = JSTYPE_VOID;
//2.判断是不是object
} else if (JSVAL_IS_OBJECT(v)) {
obj = JSVAL_TO_OBJECT(v);
if (obj &&
(ops = obj -> map -> ops,
ops == & js_ObjectOps
? (clasp = OBJ_GET_CLASS(cx, obj),
clasp -> call || clasp == & js_FunctionClass) // (3,4)
: ops -> call != 0)) { // (3)
type = JSTYPE_FUNCTION;
} else {
type = JSTYPE_OBJECT;
}
//3.判断是不是number
} else if (JSVAL_IS_NUMBER(v)) {
type = JSTYPE_NUMBER;
//4.判断是不是string
} else if (JSVAL_IS_STRING(v)) {
type = JSTYPE_STRING;
//5.判断是不是boolean
} else if (JSVAL_IS_BOOLEAN(v)) {
type = JSTYPE_BOOLEAN;
}
当对null的这个判断走到 JSVAL_IS_OBJECT(v) 的时候,哦吼,发现符合哎,然后就返回了object。
所以,到底总结:将null判断为object,是javascript在最初的设计模型中的一个漏洞。
instanseof(判断引用数据类型)
instanceof只能判断当前对象的原型链上,是否存在指定类型,返回true或false。
在instanceof的底层原理是,利用instanceof左边实例的隐式原型和instanceof右边构造函数的显示原型进行循环比对。看看是否有哪一级能对上。有对的上的就返回true,一直顺着原型链找到头都没有比对成功的,返回false。
举个例子:
//构造函数
function Parent (lang){
this.lang = lang;
}
//实例对象
const p1 = new Parent("月薪一千五,心比美式苦");
p1 instanceof Parent === true
可能还是没有那么的明白,没事。我们手写一个instanceof来解读一下👇。
//模拟instanceof功能
const myInstanceof = (el,type) => {
//获取右边构造函数的显示原型
const typeProto = type.prototype;
//进行循环查询
while(true){
//如果隐式原型为null,说明已经查找到头了。依旧没找到,说明类型不匹配,返回false。
if(el.__proto__ === null) return false;
//如果数据的隐式原型和类型的显示原型重叠,说明类型匹配,返回true
if(el.__proto__ === typeProto) return true;
//当前原型链层不为null也没有匹配,则去下一层继续比对
el = el.__proto__;
}
}
然后咱们用自己手写的myInsatnceof来判断一下类型
完美。
现在你是否已经了解的instanceof的原理了?
然后我们聊聊到一个关于instanceof在网上最常见的话题,很多小伙伴会笼统的告诉我们:instanceof只能比对array这种引用类型数据。 这句话没错,但是这不代表instanceof在就只能判断array,object和set等类型。
举个例子
哇哦,我比对Number居然成功了。
揭晓答案
大家是否记得,new操作符中有一串代码的作用是将新对象的隐式原型指向构造函数的显示原型。而我们的instanceof又是通过原型链去判断类型的。
对,我new 了一个Number。将num1变成了一个以对象形式存在的数字。
注意,我们直接赋值javascript自动推导出来的number和new出来的number不是一个东西。使用new构造的数字本质上,是一个对象,而非number。
所以呀,有时候有人告诉我们不要使用new去构造Number类型数据。就是因为数据类型会被改变。从而对比对造成一定的问题隐患。
使用instanceof比对复杂数据类型存在的弊端
因为instanceof是通过原型链去找的,但是所有原型的最顶端,永远都是Object类型。所以,就造成了insatnceof的答案并不具有唯一性。
举个例子👇
这种问题尤其在我们需要区分当前类型是对象还是其他引用类型数据的时候最麻烦。
像以上这种业务逻辑下,永远都只能进入第一个判断。虽然我们可以通过代码逻辑等手段避免以上情况,但常在河边走,哪有不失足,在有更好的比对方法的情况下,没有必要去承担这种风险。
Object.prototype.toString(全能型选手)
Object.prototype.toString一定是我们工作中使用的最多的类型判断方法,和typeof还有instanceof不一样的是,Object.prototype.toString可以判断任何类型的数据,并且结果具有唯一性。
举个例子👇
每种类型都有自己唯一的答案。
值得注意的是,Object.prototype.toString需要配合.call()函数使用。Object.prototype.toString用于判断返回当前数据的类型,而.call用于改变Object.prototype.toString中的this指向。
注意,判断类型的功能只有Object.prototype.toString这个函数具有。其他类型上的toString都是Obejct构造函数的实例,都分别重写了toString功能。
栗子👇
typeof/instanceof/object.prototype.toString的应用场景
一般的开发中,使用object.prototype.toString来判断数据的准确类型一定最稳妥。
typeof更适合判断当前类型是否为基本数据类型或引用数据类型这种大纲。我曾遇到过一种需求。就是判断数据类型。基本数据类型才给通过。
//判断当前数据是否是基本数据类型
if(typeof query !== "object" || query === null)
//判断当前数据类型是否是引用数据类型
if(typeof query === "object" || typeof query === "function")
instanceof这个,emmmm,其实我个人基本不用。
你都看到这里了,不送点封装好的类型判断给你当见面礼,都不好意思。获取封装好的类型判断 上面的文章链接里有我自己使用object.prototype.toString封装好的类型判断。
转载自:https://juejin.cn/post/7197990402720235576