第一种:type of
1.判断原始类型
我们来看一段代码
js
console.log(typeof(23))
console.log(typeof (NaN))
console.log(typeof (true))
console.log(typeof ('Hello world'))
console.log(typeof (null))
console.log(typeof (undefined))
我们来看一下执行结果
type of 可以判断除了null以外的所有原始类型
2.判断引用类型
js
let obj ={}
let arr=[]
let date= new Date()
let fn =function(){
}
console.log(typeof (obj))
console.log(typeof (arr))
console.log(typeof (date))
console.log(typeof (fn))
来看一下执行结果
type of 除了function 之外,所有引用类型都会判断成object
问题来了,为什么会这样呢?
原理:事实上,计算机在用type of 判断数据类型时,首先会把数据转化为二进制
原始类型转化二进制前三位一定不是0
引用类型转化为二进制前三位一定是0
type of 总共三句话
1.type of 可以判断除null之外所有的原始类型
2.type of 除了function 其他所有的引用类型都会被判成object
3.type of是通过将值转化为二进制后判断二进制前三位是否为0,是则为object
第二种:instance of
我们来看一段代码
js
let num =123
let string ='hello'
let boolen =true
let arr=[]
let obj={}
console.log(num instanceof Number)
console.log(string instanceof Number)
console.log(boolen instanceof Number)
console.log(arr instanceof Object)
console.log(obj instanceof Object)
执行结果
说明 instance of 只能判断引用类型数据类型,不能判断基本数据类型
同样的,我们得搞清楚其中的原理
有小伙伴可能会疑惑,为什么 arr instanceof Object 也是true呢?arr是怎么关联到Object的呢?
事实上,这涉及到原型
js
arr.__proto__= Array.prototype
Array.prototype.__proto__=Object.prototype
这就解决了上面那个问题,为什么instance of 不能判断原始类型呢?
因为原始数据类型没有原型,只有引用数据类型有原型
所以instance of 只能判断引用类型
如果没有instance of ,面试官让你手写以一个类似的 instance of 方法,你该怎么办呢?
js
function myInstanceof (L,R){
while(L!== null){
if(L.__proto__===R.prototype){
return true
}
L=L.__proto__
}
return false
}
myInstanceof([],Array)
- 函数接收两个参数:
L
和R
。在这里,L
表示要检查的对象(左操作数),而R
表示构造函数(右操作数)。 - 在
while
循环中,条件是L !== null
。这是因为当对象的原型链走到尽头时,它会指向null
。只要L
不是null
,循环就会继续。 - 在循环体内,使用
if
语句来检查L
对象的原型(L.__proto__
)是否等于R
构造函数的原型(R.prototype
)。如果相等,这意味着L
实例化自R
或其原型链上的某个构造函数,所以函数返回true
。 - 如果在当前层级没有找到匹配,将
L
设置为其自身的原型(L.__proto__
),然后循环继续到下一层级。 - 如果循环结束都没有找到匹配,说明
L
的原型链上没有任何构造函数的原型与R.prototype
相等,因此函数返回false
。
第三种:Object.prototype.toString.call()
万能方法:可以判断所有类型
原理:
具体原理
在toString方法被调用时,会执行以下几个操作步骤~
- 获取
this指向
的那个对象的[[Class]]
属性的值。 (这也是我们为什么要用call改变this指向的原因) - 计算出三个字符串"[object "、 第一步的操作结果Result(1)、 以及 "]" 连接后的新字符串。
- 返回第二步的操作结果Result(2),也就是类似
[object className]
这种格式字符串。
[[Class]]
类属性
对象的类属性(class attribute)是一个字符串,用以表示对象的类型信息。ES3和ES5都没有提供设置这个属性的方法,并只有一种间接的方法可以查询到它。默认的toString方法(继承自Object.prototype)返回了如下格式的字符串:[object class] 因此,想要获得对象的类,可以调用对象的toString方法,然后提取已返回字符串的第8个到倒数第2个位置之间的字符。
javascript
js
Object.prototype.toString.call(target).slice(8, -1);
综上,[[Class]]
是一个字符串值,表明了该对象的类型。他是一个内部属性,所有的对象(原生对象和宿主对象)都拥有该属性,且不能被任何人修改。在规范中,[[Class]]
是这么定义的:内部属性 描述。
宿主对象也包含有意义的"类属性",但这和具体的JavaScript实现有关。
因为js中每个类型都有自己私有的[[Class]]
属性,而且这个class是不能被任何人修改的,所以tostring
方法是检测属性类型最准确的方法,比instanceof
还准确。
他也可以细分内置构造函数创建的类对象:
通过内置构造函数(Array、Date等)创建的对象包含"类属性"(class attribute),他与构造函数的名称相匹配(这里也是我从contructor.name来区分数据类型的启发点)。
但是,他无法区分自定义对象类型。
通过对象直接量和Object.create创建的对象的类属性是"object",那些自定义构造函数创建的对象也是一样。类属性都是"Object",因此对于自定义类来说,没办法通过类属性来区分对象的类。
为什么用call
这里用call
是为了改变toString
函数内部的this
指向,其实也可以用apply
。
之所以必须改变this指向是因为,toString
内部是获取this
指向那个对象的[[Class]]
属性值的,如果不改变this
指向为我们的目标变量,this
将永远指向调用toString
的prototype
。
另外也是因为,很多对象继承的toString
方法重写了,为了能调用正确的toString
,才间接的使用call/apply
方法。
代码演示:
javascript
js
复制代码
Object.prototype.toString = function () {
console.log(this);
};
const arr1 = [];
Object.prototype.toString(arr1); // 打印(即this指向) Object.prototype
Object.prototype.toString.call(arr1); // 打印(即this指向) arr1
为什么null也能判断?undefined和null这两个原始值不是没有属性值吗?
因为每一个类型都有自己唯一的特定 类属性 (class attribute
) 标识,null也有、undefined也有。
该方法判断类型的缺陷
他虽然判断类型完善,但也不是没有缺点,主要有两点:
-
tostring会进行
装箱操作
,产生很多临时对象 (所以真正进行类型转换时建议配合typeof
来区分是对象类型还是基本类型,见最后代码) -
他无法区分自定义对象类型 ,用来判断这类对象时,返回的都是
Object
(针对"自定义类型"可以采用instanceof
区分)
总结一下
1. typeof
- 用于判断原始类型(除
null
外),能识别number
,string
,boolean
,undefined
和function
。 - 对引用类型(除
function
外)返回object
。
2. instanceof
- 用于判断引用类型。
- 检查对象是否为某构造函数的实例。
- 通过原型链查找,无法判断原始类型。
3. Object.prototype.toString.call()
- 万能方法,可判断所有类型,包括
null
和undefined
。 - 返回格式
[object Type]
。 - 缺点:对自定义对象类型返回
Object
,存在临时对象开销。
总结:typeof
用于基本类型和函数,instanceof
用于引用类型,Object.prototype.toString.call()
是最全面的方法。