JS中的类型检测
# 0.前言
在手写系列(深拷贝
、手写instanceof
、手写bind
)中,常需要对数据的类型进行判断,在 JS
中,判断数据类型的方式有很多,以判断数组(Array)
为例:
Array.isArray
instanceof
Object.prototype.toString(Array)
(误)
typeof
本篇博客就尽量对这块内容做详细的说明。
# 1. typeof(简单数据类型)
在JS
中一共有八种数据类型:
- 简单数据类型(可以使用
typeof
进行拆分)undefined
boolean
string
number
bigInt
:长整形
null
:比较特殊,typeof
竟然识别为object
object
(不可以使用typeof
判断)function
:比较特殊,typeof
可以识别。Symbol
数据类型
在八种数据类型中,
typeof
可以对简单数据类型进行筛选区分,在一般情况下,我们 可以直接使用instanceof
将数据类型一分为二:即 object 与 非 object。此外,我们可以巧用
typeof
对function
和null
进行判断。function
是复杂数据类型,但是typeof
也可以识别。而null
是简单数据类型,但却被识别为object
。这两个是官方承认的
typeof
的行为上的失误。——出自《现代 JavaScript 教程》 (opens new window)
# 2. Object.prototype.toString(无所不能)
PS:最近使用
Object
原型的方法判断数据类型的文章好多,一篇是微信公众号高级前端进阶《阅读axios源码》 (opens new window)。还有一个B站的一个视频中也用了这种方法。
# 常见类型判断:
// Boolean 类型,tag 为 "Boolean"
Object.prototype.toString.call(true); // => "[object Boolean]"
// Number 类型,tag 为 "Number"
Object.prototype.toString.call(1); // => "[object Boolean]"
// String 类型,tag 为 "String"
Object.prototype.toString.call(""); // => "[object String]"
// Array 类型,tag 为 "String"
Object.prototype.toString.call([]); // => "[object Array]"
// Arguments 类型,tag 为 "Arguments"
Object.prototype.toString.call((function() {
return arguments;
})()); // => "[object Arguments]"
// Function 类型, tag 为 "Function"
Object.prototype.toString.call(function(){}); // => "[object Function]"
// Error 类型(包含子类型),tag 为 "Error"
Object.prototype.toString.call(new Error()); // => "[object Error]"
// RegExp 类型,tag 为 "RegExp"
Object.prototype.toString.call(/\d+/); // => "[object RegExp]"
// Date 类型,tag 为 "Date"
Object.prototype.toString.call(new Date()); // => "[object Date]"
// 其他类型,tag 为 "Object"
Object.prototype.toString.call(new class {}); // => "[object Object]"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Object.prototype.toString
除了可以做类型判断外,还可以对更多复杂的数据进行判断:
# isFile
判断文件类型
function isFile(val) {
return Object.prototype.toString.call(val) === '[object File]';
}
2
3
# isBlob
判断Blob
function isBlob(val) {
return Object.prototype.toString.call(val) === '[object Blob]';
}
2
3
# 3. instanceof
(复杂数据类型)
instanceof
就是判断构造函数的 prototype
属性是否出现在实例的原型链上。
// 下面:left是实例 | right是构造函数
function instanceOf(left, right) {
let proto = left.__proto__
while (true) {
if (proto === null) return false
if (proto === right.prototype) {
return true
}
proto = proto.__proto__
}
}
2
3
4
5
6
7
8
9
10
11
上面:获取实例的原形以及构造函数的原形都可以替换成:
Object.getPrototypeOf(实例||构造函数)
# 4.Array.isArray
(考虑到多个执行环境)
参见MDN写法:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
在 instanceof
判断数据类型时,假定了只有单一的全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array
构造函数。
如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。
Array.isArray
就是为了解决在不同执行环境下依然能准确的判断该值是否是数组。
在下面这个例子中,人为的创造了一个 iframe
框架环境,然后故意使用新环境下的Array
创建的实例,使用 instanceof
去判断。
// 创造一个iframe环境
var iframe = document.createElement('iframe')
// 将 iframe 插入到页面中
document.body.appendChild(iframe)
// 取出iframe中的Array
var newArray = window.frames[window.frames.length-1].Array
// 使用iframe中的Array创建一个新的数组
var arr = new newArray(1,2,3) // [1,2,3]
// arr 是 newArray的实例,而不是Array的实例
arr instanceof Array // false
Array.isArray(arr) // true
2
3
4
5
6
7
8
9
10
11
12
# 5.总结
- 对于简单数据类型(除了
null
)的判断,可以使用typeof
区分。 - 对于
Function
数据类型的判断,也可以使用typeof
进行区分(巧用:typeof
的设计失误) - 对于复杂数据类型:使用
instanceof
Object.prototype.toString.call()
万金油方法,尽管网上有评论说该函数兼容性存在问题,既然大名鼎鼎的axios
都使用这种方式进行判断,也可以安心的使用。- 建议使用
Array.isArray
来判断数组类型。
# 待理解的问题
Symbol
的使用,这里已经是第二次遇到了,在另一篇博客中《深入理解forEach
与forof
》[Symbol.iterator]
可以取出对象的迭代器,从而完成异步代码的顺序调用。Object.getPrototypeOf(obj)
的使用,或者说如何去区分prototype
,getPrototypeOf
和__proto__
这三个获取原形的方式。在手写
bind
这个函数时,如果希望支持new bind
操作的话,就需要区分出该函数是否使用了new
关键字,当然可以使用instanceof
去做处理,但是推荐还是使用Object.getPrototypeOf()
的方式,具体原因还有待深入了解。