今天浏览博客时,看到一篇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); //'undefined'
console.log(typeof(true)); //'boolean'
console.log(typeof '123'); //'string'
console.log(typeof 123); //'number'
console.log(typeof NaN); //'number'
console.log(typeof null); //'object'
var obj = new String();
console.log(typeof(obj)); //'object'
var fn = function(){};
console.log(typeof(fn)); //'function'
console.log(typeof(class c{})); //'function'
【注意】需要注意的几点是:
NaN
返回number
null
返回Object
new String()的实例
返回object
class
返回function
1.3、typeof的局限性
typeof有一定的局限性,比如数组
返回的也是object
(如下图)
typeof([1,2,3]) //"object"
typeof(new Array()) //"object"
这样用来判断数组的话就不合适了,这时候可以选择使用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) {
// 获取实例对象obj的原型对象
let proto = obj.__proto__
while (true) {
if (proto === null) {
return false//原型链最终指向null,如果顺着proto一直找到null,还没找到原型,那就return false
}
if (proto === constructor.prototype) {
return true//找到原型,return true
}
//顺着__proto__继续往上找
proto = proto.__proto__
}
}
function Foo(){}
let foo = new Foo()
instanceof(foo,Foo) //true
instanceof(foo,Function) //true
instanceof(foo,Object) //true
在JS原型链中,原型链最终指向null
,所以如果顺着proto一直找到null,还没找到原型,那就return false
2.2、instanceof的使用
Object instanceof Object //true
Object instanceof Function //true
Function instanceof Object //true
Function instanceof Function //true
String instanceof Object //true
String instanceof Function //true
Number instanceof Object //true
Number instanceof Function //true
String instanceof String //false
Number instanceof Number //false
分析可知:
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 //false
- 我们新建一个函数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) //false
console.log(str2 instanceof String) //true
String('1111') instanceof String //false
由上述例子可以看出:
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() //"true"
[1,2,3].toString() //"1,2,3"
('hello world').toString() //"hello world"
({name:'tom'}).toString() //"[object Object]"
({}).toString(); // => "[object Object]"
Math.toString(); // => "[object Math]"
即如果对象的 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);
//"[object Array]"
Array.prototype.toString.call(arr);
//"1,2,3"
看到这里大家都应该明白了,其实只有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));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call(100));//[object Number]
console.log(Object.prototype.toString.call("wang"));//[object String]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call({name: "xiaohua"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([1,2,3]));//[object Array]
console.log(Object.prototype.toString.call(new Date));//[object Date]
console.log(Object.prototype.toString.call(/\d/));//[object RegExp]
function Person(){};
console.log(Object.prototype.toString.call(new Person));//[object Object]
- 可以看到这个方法能判断大多类型,但是针对我们自己创建的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"));//true
console.log(arr.toString());//7,7,7
delete Array.prototype.toString;//delete操作符可以删除实例属性
console.log(Array.prototype.hasOwnProperty("toString"));//false
console.log(arr.toString());//"[object Array]"
4、补充
在前面我们有提到,一个字符串,不是对象,但却可以调用String构造函数
上的方法,例如:
var str = 'hello world'
str.toString() //"hello world"
简单理解就是,当原始数据类型(boolean,Number、String)
在调用方法时,JS 将会创建对象
,以便调用方法属性,而在使用完毕后将会销毁该对象
。
原始值被当作构造函数创建的一个对象来使用时, JS 会将其转换为一个对象,以便其可以使用对象的特性(如方法),而后抛弃对象性质,并将它变回到原始值。
这就牵扯到基本包装类型
了,基本类型中Boolean
,Number
,String
又是基本包装类型
,这三个基本类型都有自己对应的包装对象
。包装对象,其实就是对象,有相应的属性和方法。调用方法的过程,是在后台发生的
。
细节可以参考:js 中的基本类型,引用类型,基本包装类型
本文参考博客: