提到类型判断大家首先想到的肯定是typeof和instanceof,其实还有一种不错的判断类型的方法,就是Object.prototype.toString,下面我们先来聊一聊它们的实现原理。
typeof
typeof 可以用于判断number,string,object,boolean,function,undefined,symbol这些类型。我们都知道,typeof 在用于判断一个object类型的数据时,只能告诉我们这个数据是object,而不能具体知道是哪一种object。
const arr = []
console.log(typeof arr) // object
console.log(arr instanceof Array) // true
我们需要用到instanceof来判断具体类型,接下来,我们先谈谈typeof的原理
一个js变量,在底层存储变量的时候,会在变量的机器码的低位1-3位存储自身的类型信息:
- 000:对象
- 010:浮点数
- 100:字符串
- 110:布尔
- 1:整数
另外还有undefined和null,这两个值的信息存储有点特殊,null所有的机器码均为0,undefined则用-2^30整数来表示
所以,typeof在判断null的时候就出现问题了,由于null的所有机器码均为0,因此直接被当做对象了
typeof null // object
所以在使用typeof判断变量类型的时候,我们需要注意,最好是判断基本数据类型,避免对null进行判断。
instanceof
我们都知道instanceof可以用于判断对象的具体类型,返回boolean值。
其实instanceof的原理是判断操作符右侧构造函数的原型是否在操作符左侧对象的原型链上。 乍看这句话,大家可能不太理解,instanceof实现的大概思路如下:
function new_instance_of(leftVaule, rightVaule) {
let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
while (true) {
if (leftVaule === null) {
return false;
}
if (leftVaule === rightProto) {
return true;
}
leftVaule = leftVaule.__proto__
}
}
instanceof在执行过程中,会遍历左侧变量的原型链,直到找到右侧变量的prototype,如果查找失败则会返回false。
下面,我们来看一个例子
Object instanceof Object // true
function Person() {}
Person instanceof Person // false
Person instanceof Object // true
Person instanceof Function // true
我们知道每个js对象均有一个隐式的__proto__原型属性(宿主环境为浏览器情况下可访问),而显示的原型属性是prototype,Object.peototype.__proto__的属性在未修改的情况下为null。
下面,我们来梳理一下上面的例子:
-
Object instanceof Object
Object的prototype属性是Object.prototype,而由于Object本身是一个函数,由Function创建,所以Object.__proto__的值是Function.prototype,而Function.prototype的__proto__属性又是Object.prototype,所以可以判断出为true
-
Person instanceof Person
Person的prototype属性是Person.prototype,而Person的__proto__属性是Function.prototype,Person的原型链上没有Person.prototype,所以返回false
-
Person instanceof Object
Object的原型是Object.prototype,Person的__proto__为Function.prototype, Function.prototype的__proto__为Object.prototype,所以返回true
-
Person instanceof Function
Function的原型是Function.prototype,Person的__proto__为Function.prototype,所以返回true
Object.prototype.toString.call()
使用Object.prototype上的原生toString()方法判断数据类型,使用方法如下:
//1.判断基本类型:
Object.prototype.toString.call(null);//”[object Null]”
Object.prototype.toString.call(undefined);//”[object Undefined]”
Object.prototype.toString.call(“abc”);//”[object String]”
Object.prototype.toString.call(123);//”[object Number]”
Object.prototype.toString.call(true);//”[object Boolean]”
//2.判断原生引用类型:
//函数类型
Function fn(){console.log(“test”);}
Object.prototype.toString.call(fn);//”[object Function]”
//日期类型
var date = new Date();
Object.prototype.toString.call(date);//”[object Date]”
// 数组类型
var arr = [1,2,3];
Object.prototype.toString.call(arr);//”[object Array]”
//正则表达式
var reg = /[hbc]at/gi;
Object.prototype.toString.call(reg);//”[object RegExp]”
//自定义类型
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person("Rose", 18);
Object.prototype.toString.call(arr); //”[object Object]”
//很明显这种方法不能准确判断person是Person类的实例,而只能用instanceof 操作符来进行判断,如下所示:
console.log(person instanceof Person);//输出结果为true
//3.判断原生JSON对象:
var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON);
console.log(isNativeJSON);//输出结果为”[object JSON]”说明JSON是原生的,否则不是;
//注意:Object.prototype.toString()本身是允许被修改的,而我们目前所讨论的关于Object.prototype.toString()这个方法的应用都是假设toString()方法未被修改为前提的。
我们可能会有一个疑问,就是为什么不直接使用toString方法呢
console.log("jerry".toString());// jerry
console.log((1).toString());// 1
console.log([1,2].toString());// 1,2
console.log(new Date().toString());// Wed Dec 21 2016 20:35:48 GMT+0800 (中国标准时间)
console.log(function(){}.toString());// function (){}
console.log(null.toString());// error
console.log(undefined.toString());// error
因为toString为Object的原型方法,而Array,Function等类型都重写了toString方法。不同对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法,而不是去调用Object上原型toString方法。所以,在想要得到对象的具体类型时,我们应该调用Object上原型的toString方法。我们可以验证一下,将数组的toString方法删除:
const arr = [1, 2, 3]
console.log(Array.prototype.hasOwnProperty('toString')) // true
console.log(arr.toString()) // '1,2,3'
delete Array.prototype.toString
console.log(Array.prototype.hasOwnProperty('toString')) // false
console.log(arr.toString()) // "[object Array]"
要获取一个变量的真实内置类型,只能通过Object.prototype.toString来访问,因此,通过.call改变toString方法的this指向,从而获得对象的内置类型
总结
typeof用来判断基本类型数据是没什么问题的,不过需要注意判断null时会产生的问题。如果想要判断一个对象的具体类型可以考虑使用instanceof,但是instanceof也可能不准确,比如一个数组,它也可以被判断为Object。所以我们想要比较准确的判断对象实例的类型时,我们可以用Object.prototype.toString.call方法。