JavaScript
数据类型的判断

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:整数

因为对于 undefinednull 来说,这两个值的信息存储是有点特殊的,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)