instanceof 和 typeof
在 JavaScript 中,typeof
一般被用于判断一个变量的类型,我们可以利用 typeof
来判断 number
, string
, object
, boolean
, function
, undefined
, symbol
这七种类型但是 typeof
有一个缺点,就是无法准确的判断对象类型,因为 typeof
对于所有的对象类型都会返回 object
,比如:
let s = new String("abc");
typeof s === "object"; // true
s instanceof String; // true
这是因为 js 在底层存储变量的时候,会在变量的机器码的低位 1-3 位存储其类型信息。
- 000:对象
- 010:浮点数
- 100:字符串
- 110:布尔
- 1:整数
因为对于 undefined
和 null
来说,这两个值的信息存储是有点特殊的,null
的机器码都是 0,而 undefined
用 −2^30 整数来表示,所以 typeof
在判断 null
的时候就出现问题了,由于 null
的所有机器码均为 0,因此直接被当做了对象来看待。
然而用 instanceof 来判断的话
null instanceof null; // TypeError: Right-hand side of 'instanceof' is not an object
instanceof 操作符的主要实现原理就是只要右边变量的 prototype
在左边变量的原型链上即可。因此,instanceof
在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype
,如果查找失败,则会返回 false
,告诉我们左边变量并非是右边变量的实例。
Object.prototype.toString().call()
使用 Object.prototype.toString().call()这个方法却可以完美的判断数据类型。
Object.prototype.toString.call(1); // "[object Number]"
Object.prototype.toString.call("1"); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(Symbol(1)); // "[object Symbol]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(function () {}); // "[object Function]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(/a/g); // "[object RegExp]"
Object.prototype.toString.call(new Error()); // "[object Error]"
Object.prototype.toString.call(document); // "[object HTMLDocument]"
Object.prototype.toString.call(window); // "[object Window]"
为什么 Object.prototype.toString.call()可以准确判断数据类型
我们需要了解,不论是 Array,还是 Date,所有数据类型。都是从对象衍生而来的。本质上,Array 和 Date 还有 Function 啥的他们就是对象。
虽然他们都被称为对象,对象也是有很多类型的。比如 Date,他就是时间对象‘ [object Date] ’, Array,他就是数组对象‘[object Array]’等等。简而言之,js 中所有的数据类型,都只是对象的一种类型。所以,js 中有一句话叫,万物皆对象。
而 Object.prototype.toString() 这个函数作用就是,返回当前调用者的对象类型。
tc39 对 Object.prototype.toString 的说明 (opens in a new tab):
Historically, this method was occasionally used to access the String value of the [[Class]] internal slot that was used in previous editions of this specification as a nominal type tag for various built-in objects. The above definition of toString preserves compatibility for legacy code that uses toString as a test for those specific kinds of built-in objects. It does not provide a reliable type testing mechanism for other kinds of built-in or program defined objects. In addition, programs can use @@toStringTag in ways that will invalidate the reliability of such legacy type tests.
Object.prototype.toString()会返回[object, [[class]]]的字符串
其中[[class]]会返回 es 定义的对象类型,包含"Arguments", “Array”, “Boolean”, “Date”, “Error”, “Function”, “JSON”, “Math”, “Number”, “Object”, “RegExp”, 和 “String”;再加上 es5 新增加的返回[object Undefined]和[object Null]
言简意赅的说:所有数据类型都是对象的一种类型,而 Object.prototype.toString 可以返回当前调用者的对象类型。
Object.prototype.toString.call()为什么要加 call();
因为 Object.prototype.toString()返回的是调用者的类型。不论你 toString()本身的入参写的是什么,在 Object.prototype.toString()中,他的调用者永远都是 Object.prototype;所以,在不加 call()情况下,我们的出来的结果永远都是 '[object Object]'
call(),是为了改变 Object.prototype.toString 这个函数都指向。让 Object.prototype.toString 这个方法指向我们所传入的数据。
为什么一定要用 Object.prototype.toString.call()
有些小伙伴可以回疑惑了。为什么一定要用 Object.prototype.toString.call()这个方法,那么长,写起来很麻烦,我直接在当前数据本身去调用 toString(),然后让他顺着原型链去找,最后找到 Object.prototype.toString 这个方法不行吗?连 call 都省下了。
还真不行。
因为,每个数据类,他们都重写了 toString()方法。所以,如果我们拿数据本身去 toString(),是得不到对象类型的。
总结
- js 中所有的数据类型,本质上都是对象,而这些数据类型不过是对象的一种类型而已。
- typeof 只能判断基本数据类型,不能判断 Array, Date, RegExp, Error, null,受影响导致无法准确判断 function 和 Object。
- instanceof 只能判断对象右边的变量是否在左边变量的原型链上。
- Object.prototype.toString 这个方法是用于返回当前调用者的对象类型的
- call 是为了让 Object.prototype.toString 方法指向我们指定的数据。否则返回永远都是[object Object]
参考
https://juejin.cn/post/7116114617834668062 (opens in a new tab)