今天浏览博客时,看到一篇JS中对类型判断讲解的文章,感觉写的听好,正好平时在工作中也经常遇到判断类型的情况,所以就想记录一下。
1、typeof
众所周知,js是一门弱语言 ,它在声明变量时无需确定变量的类型,js在运行时会自动判断。那么如何判断一个变量的类型呢,js提供了typeof运算符 。
1.1、typeof的作用
typeof运算符:是用来检测一个变量的类型 , 把类型信息 当作字符串 返回,值包括如下6 种:
- undefined:表示
未定义 的变量或值 - boolean:表示
布尔类型 的变量或值 - string:表示
字符串类型 的变量或值 - number:表示
数字类型 的变量或值 - object:表示
对象类型 的变量或值,或者null (这个是js历史遗留问题,将null作为object类型处理) - function:表示
函数类型 的变量或值
1.2、typeof的使用
示例:
console.log(typeof a);
console.log(typeof(true));
console.log(typeof '123');
console.log(typeof 123);
console.log(typeof NaN);
console.log(typeof null);
var obj = new String();
console.log(typeof(obj));
var fn = function(){};
console.log(typeof(fn));
console.log(typeof(class c{}));
【注意】需要注意的几点是:
NaN 返回number null 返回Object new String()的实例 返回object class 返回function
1.3、typeof的局限性
typeof有一定的局限性,比如数组 返回的也是object (如下图)
typeof([1,2,3])
typeof(new Array())
这样用来判断数组的话就不合适了,这时候可以选择使用instanceof
2、instanceof
2.1、instanceof的作用
instanceof 运算符:是用来测试一个实例对象 在其原型链 中是否存在一个构造函数 的 prototype 属性。
官方解释:
The instanceof operator tests whether an object has in its prototype chain the prototype property of a constructor.
从字面意思理解,就是判断一个对象(实例)的原型链上是否存在一个构造函数的prototype 属性,也就是顺着__proto__一直找prototype
判断constructor.prototype是否出现在obj的原型链上:
function instanceof (obj, constructor) {
let proto = obj.__proto__
while (true) {
if (proto === null) {
return false
}
if (proto === constructor.prototype) {
return true
}
proto = proto.__proto__
}
}
function Foo(){}
let foo = new Foo()
instanceof(foo,Foo)
instanceof(foo,Function)
instanceof(foo,Object)
在JS原型链中,原型链最终指向null ,所以如果顺着proto一直找到null,还没找到原型,那就return false
2.2、instanceof的使用
Object instanceof Object
Object instanceof Function
Function instanceof Object
Function instanceof Function
String instanceof Object
String instanceof Function
Number instanceof Object
Number instanceof Function
String instanceof String
Number instanceof Number
分析可知:
Object.__proto__ 指向 Function.prototype ,而Function.prototype.__proto__ 指向Object.prototype ,所以前两个为trueFunction.__proto__ 指向Function.prototype ,而Function.prototype.__proto__ 指向Object.prototype ,所以3和4也为trueString 和Number 是函数,也都是先指向Function.prototype ,再指向Object.prototype ,所以5,6,7,8也都为true- Number instanceof Number和String instanceof String,为false,因为
Number.__proto__ 和Number.prototype 完全是两回事,String同理
如果对于上面最后2个例子还是不理解,即Number.__proto__ 和Number.prototype 是两回事(String同理),可以看下面:
function abc(){}
abc instanceof abc
- 我们新建一个函数abc,则
abc instanceof abc 的意思就是看左边abc.__proto__是否存在abc.prototype ,很明显,答案是false。 - 因为
左边abc.__proto__ 是Function.prototype ,而Function.prototype.__proto__ 又指向了Object.prototype ,所以最终只能找到Object.prototype ,但是右边是Foo.prototype ,无法相等,结果是false
这样解释一下应该就好理解了。
基本数据类型:
对于一些基本数据类型:String 、Number 、Boolean 、Undefined 、Null 、Symbol 这种,最好不要用instanceof ,因为根据instanceof的定义来看,instanceof是针对的an object(实例对象) 的,所以判断基本数据类型的时候可能发生错误 。
let str1 = '1111'
let str2 = new String('1111')
console.log(str1 instanceof String)
console.log(str2 instanceof String)
String('1111') instanceof String
由上述例子可以看出:
str1是基本数据类型 ,并不是一个对象,也不是由String实例化出来的,所以str1不是String的实例str2是使用String构造的实例化对象 String('1111')没有使用new ,所以并不是使用构造函数实例化 ,而是使用String这个函数返回了一个字符串1111, 所以为false
【注意】 instanceof 语法规定:object instanceof constructor ,规定确认对象为object,所以左边如果不是对象的话,一定会返回false;
null比较特殊,虽然null是Object类型,但是null instanceof Object也是false ,因为null是空对象不具有任何对象的特性 ,上面也没有__proto__属性 。
3、Object.prototype.toString()
3.1、Object.prototype.toString()的作用
Object.prototype.toString()方法:会返回一个形如 "[object XXX]" 的字符串,可以用来判断数据类型 。
不过在说Object.prototype.toString()之前,我们先说一下toString() 方法和Object.prototype.toString.call() 方法
3.2、toString() 和 call()
MDN官网的解释是:toString() 方法返回一个表示该对象的字符串。
true.toString()
[1,2,3].toString()
('hello world').toString()
({name:'tom'}).toString()
({}).toString();
Math.toString();
即如果对象的 toString() 方法未被重写 ,就会返回相应的字符串 。
但是,大多数对象toString() 方法都是重写 了的,并不是 Object.prototype 中的 toString() 方法,这时就需要用 call() 方法来调用。
var arr=[1,2];
//直接对一个数组调用toString()
arr.toString();// "1,2"
//通过call指定arr数组为Object.prototype对象中的toString方法的上下文
Object.prototype.toString.call(arr); //"[object Array]"
为什么toString()会有不同的作用呢?这里我们不做深入的研究,只是简单明白这涉及到js原型及原型链相关知识即可,比如下面的例子:
var arr=[1,2,3];
Object.prototype.toString.call(arr);
Array.prototype.toString.call(arr);
看到这里大家都应该明白了,其实只有Object.prototype上的toString 才能用来进行复杂数据类型的判断
- JS中的
对象都继承自Object ,所以当我们在某个对象上调用一个方法 时,会先在该对象上 进行查找,如果没找到 则会进入对象的原型(也就是.prototype)进行查找 ,如果没找到,同样的也会进入对象原型的原型 进行查找,直到找到或者进入原型链的顶端Object.prototype 才会停止 - 所以,当我们使用arr.toString()时,不能进行复杂数据类型的判断,因为它调用的是Array.prototype.toString,虽然Array也继承自Object,但JS
在Array.prototype上重写了toString - 而我们通过
toString.call(arr) 实际上是通过原型链调用了Object.prototype.toString 方法
3.3、精确判断对象的类型
console.log(Object.prototype.toString.call(undefined));
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(100));
console.log(Object.prototype.toString.call("wang"));
console.log(Object.prototype.toString.call(true));
console.log(Object.prototype.toString.call({name: "xiaohua"}));
console.log(Object.prototype.toString.call(function(){}));
console.log(Object.prototype.toString.call([1,2,3]));
console.log(Object.prototype.toString.call(new Date));
console.log(Object.prototype.toString.call(/\d/));
function Person(){};
console.log(Object.prototype.toString.call(new Person));
- 可以看到这个方法能判断大多类型,但是针对我们自己创建的function,比如Person,返回的也是[object object],其余场景基本都覆盖到了,可以截取前8位到最后一位,作为类型判断
3.4、面试题
问:Object.prototype.toString和Object.toString是一回事吗?
答:
- 不是。 因为
Object.toString 是Object.__proto__.toString ,也就是去上一级的原型 中拿的方法,是Function.prototype.toString ,和Object.prototype.toString 根本不是同一个函数 。 - 同理
Number ,String ,Array 里的toString也和Object.prototype.toString不一样 ,而且它们也都被重写了toString 方法。
可以测试一下,如果删除Array上的toString,我们知道顺着原型链一直找会找到Object.prototype上,这时候会调用Object原型上的toString了,通过控制台打印可以看到输出的是[object Array]。
var arr=[7,7,7];
console.log(Array.prototype.hasOwnProperty("toString"));
console.log(arr.toString());
delete Array.prototype.toString;
console.log(Array.prototype.hasOwnProperty("toString"));
console.log(arr.toString());
4、补充
在前面我们有提到,一个字符串,不是对象,但却可以调用String构造函数 上的方法,例如:
var str = 'hello world'
str.toString()
简单理解就是,当原始数据类型(boolean,Number、String) 在调用方法时,JS 将会创建对象 ,以便调用方法属性,而在使用完毕后将会销毁该对象 。
原始值被当作构造函数创建的一个对象来使用时, JS 会将其转换为一个对象,以便其可以使用对象的特性(如方法),而后抛弃对象性质,并将它变回到原始值。
这就牵扯到基本包装类型 了,基本类型中Boolean ,Number ,String 又是基本包装类型 ,这三个基本类型都有自己对应的包装对象 。包装对象,其实就是对象,有相应的属性和方法。调用方法的过程,是在后台发生的 。
细节可以参考:js 中的基本类型,引用类型,基本包装类型
本文参考博客:
|