typeof

typeof 操作符返回一个表示数据类型的字符串,它可以应付常规场景下的数据类型判断。对基本数据类型 undefined, boolean, string, number 和引用数据类型 function 都可以正确判断,但是对 null,数组,对象则统一返回 “object”。也就是说,typeof 不适合用来判断引用数据类型。

var a = 1
typeof a // "number"
var b = [1,2,3]
typeof b // "object"
var c = {}
typeof c // "object"

instanceof

instanceof 解决了上述问题,它的原理就是判断右操作数(通常是构造函数)的原型对象是否出现在左操作数(通常是实例)的原型链上,如果在则返回 true。据此可以判断引用数据类型具体是哪种类型。

var b = [1,2,3]
b instanceof Array // true
var c = {}
c instanceof Object // true

需要注意的是,instanceof 对于不是通过 new 创建的基本数据类型无法做出正确的判断:

var a = 1
a instanceof Number // false

这是因为此时的 a 仅仅是一个基本类型的值,而不是实例对象,如果我们通过 new 创建 a,那么就能正确判断

var a = new Number(1);
a instanceof Number // true

结合 instanceof 的原理其实就很好理解其中原因了。

>> 另外要注意,instanceof 这个方法并非百试百灵 —— 假定脚本中有多个全局环境,例如 html 中有多个子 iframe,那么对于每一个全局环境而言,它都有自己版本的构造函数,进而有自己版本的原型链。instanceof 左右两边的操作数来自于不同全局环境时,即使实例和构造函数对应,也只会返回 false。

Object.getPrototypeOf()

let arr = [1,2];
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true

利用原型链。存在同上问题。

isPrototypeOf()

let arr = [12];
console.log(Array.prototype.isPrototypeOf(arr)); // true

利用原型链。存在同上问题。

Object.prototype.toString.call()

let arr = [12];
`Object.prototype.toString.call(arr)`; //"[object Array]"

这个方法基本很完善,原理就是:在任何值上调用 Object 原生的 toString() 方法,都会返回一个格式为 [object NativeconstructorName] 的字符串。据此可以准确判断任何值的数据类型。

这里注意几个点:

  1. arr 作为对象,也是 Object 的一个实例,为什么不直接使用 arr.toString()?这是因为它的这个方法被重写了,即 Array.prototype.toString()。在使用 arr.toString() 的时候,它优先在原型链上找到并调用了重写的方法,最后输出的是 "1,2"

  2. 对象字面量调用 toString() 的时候则依然输出 "[object Object]",这是因为它没有重写这个方法,所以找到的是 Object.prototype 的该方法。

  3. 同样的,函数对象的 toString() 方法也被重写了,即 Function.prototype.toString()。调用的时候返回一个表示当前函数源代码的字符串。当对内置函数对象调用该方法时,返回如下格式的字符串:

Object.toString();

// "function Object() {
//    [native code]
// }"

Array.toString(); 
// "function Array() { 
//    [native code] 
// }"

实际上,这里的 Object 是构造函数,既然是函数,就可以看作是 Function 构造函数实例化的对象,因此这里相当于函数对象调用了 toString() 方法,也就是调用的 Function.prototype.toString() 方法。