JS中的类型检测
# 0.前言
在手写系列(深拷贝、手写instanceof、手写bind)中,常需要对数据的类型进行判断,在 JS 中,判断数据类型的方式有很多,以判断数组(Array)为例:
Array.isArrayinstanceofObject.prototype.toString(Array)(误)
typeof
本篇博客就尽量对这块内容做详细的说明。
# 1. typeof(简单数据类型)
在JS中一共有八种数据类型:
- 简单数据类型(可以使用
typeof进行拆分)undefinedbooleanstringnumberbigInt:长整形
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()的方式,具体原因还有待深入了解。