一、Object.prototype.toString.call(obj)
使用Object
原型对象上的toString
方法是我们开发中常用的判断数据类型方案,它不仅能判断数组还能判断基础类型或者其他引用类型,在vue2源码中作者也是有大量使用这个方案来判断数据类型
vue2源码:vue/src/shared/util.js
javascript
...
/**
* Quick object check - this is primarily used to tell
* Objects from primitive values when we know the value
* is a JSON-compliant type.
*/
export function isObject (obj: mixed): boolean %checks {
return obj !== null && typeof obj === 'object'
}
/**
* Get the raw type string of a value e.g. [object Object]
*/
const _toString = Object.prototype.toString
export function toRawType (value: any): string {
return _toString.call(value).slice(8, -1)
}
/**
* Strict object type check. Only returns true
* for plain JavaScript objects.
*/
export function isPlainObject (obj: any): boolean {
return _toString.call(obj) === '[object Object]'
}
export function isRegExp (v: any): boolean {
return _toString.call(v) === '[object RegExp]'
}
...
使用案例
我们先来简单回顾下使用案例,下面代码可以看出这种方案还是非常强大的,不仅基础数据类还是引用数据类型,甚至是正则对象,时间对象,Error对象等都能准确判断出来。
javascript
const _toString = Object.prototype.toString;
console.log(_toString.call({})); // [object Object]
console.log(_toString.call([])); // [object Array]
console.log(_toString.call(1)); // [object Number]
console.log(_toString.call("abc")); // [object String]
console.log(_toString.call(false)); // [object Boolean]
console.log(_toString.call(function () {})); // [object Function]
console.log(_toString.call(null)); // [object Null]
console.log(_toString.call(undefined)); // [object Undefined]
console.log(_toString.call(Symbol(""))); // [object Symbol]
console.log(_toString.call(BigInt(9007199254740991))); // [object BigInt]
console.log(_toString.call(new Date())); // [object Date]
console.log(_toString.call(new RegExp())); // [object RegExp]
console.log(_toString.call(new Error())); // [object Error]
console.log(_toString.call(new Map())); // [object Map]
console.log(_toString.call(new Set())); // [object Set]
缺陷:[Symbol.toStringTag]
先来简单了解一下这个API的官方定义:
Symbol.toStringTag
内置通用(well-known)symbol 是一个字符串值属性,用于创建对象的默认字符串描述。它由Object.prototype.toString()
方法内部访问。
大家可能看得一脸懵逼,其实简单来说就是使用这个API可以自定义Object.prototype.toString()
的返回结果。在[Symbol.toStringTag]
这个API出现后,这种判断数据类型的方式变得不再那么稳健了。请看下面例子:
javascript
function isArray(array) {
const _toString = Object.prototype.toString;
return Object.prototype.toString.call(array) === "[object Array]";
}
class ValidatorClass {
get [Symbol.toStringTag]() {
return "Array";
}
}
const validatorObj = new ValidatorClass();
console.log(Object.prototype.toString.call(validatorObj)) //[object Array]
console.log(isArray(validatorObj)) // true
const obj = {
[Symbol.toStringTag]: "Array",
};
console.log(isArray(obj)); // true
我们通过[Symbol.toStringTag]
将返回值设置成Array
,再使用Object.prototype.toString
就会被错误 判断成当前数据类型为数组
二、instanceof
instanceof
运算符 用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上。
实现原理
instanceof
的原理就是在该数据的原型链上看能否找到对应的prototype
对象,以下代码是instanceof
的简单实现代码:
ini
function myInstanceOf(left, right) {
let leftValue = left.__proto__;
let rightValue = right.prototype;
while (true) {
if (leftValue === null) {
return false;
}
if (leftValue === rightValue) {
return true;
}
leftValue = leftValue.__proto__;
}
}
缺陷一:手动更改prototype
instanceof
也是判断数据类型的一种方案,但同样的也是存在缺陷,请看下面例子:
javascript
function isArray(obj) {
return obj instanceof Array;
}
console.log(isArray([])); // true
const obj = {};
Object.setPrototypeOf(obj, Array.prototype);
console.log(isArray(obj)) // true
我们通过Object.setPrototypeOf
将数组的prototype设置到了普通对象obj的原型链上,这样instanceof也会错误 地判断当前数据为数组
关于Object.setPrototypeOf
,你可以查看以下链接查看更详细使用内容: Object.setPrototypeOf
缺陷二:不同环境的Array
还有一种特殊情况,假如我们页面中存在一个iframe
窗口,使用iframe
窗口中的Array
放在当前页面来判断也是会出错的。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<iframe src="" frameborder="0"></iframe>
</body>
</html>
javascript
function isArray(obj) {
return obj instanceof Array;
}
const frame = document.querySelector("iframe");
const frameArray = frame.contentWindow.Array;
const arr = new frameArray(1, 2, 3);
console.log(frameArray); // ƒ Array() { [native code] }
console.log(arr) // [1, 2, 3]
console.log(isArray(arr)) // false
三、constructor
同样,我们还能使用构造函数constructor
能判断数据类型,但是问题跟上面的instanceof
是一样的。
ini
const obj = {};
Object.setPrototypeOf(obj, Array.prototype);
console.log([].constructor === Array); // true
console.log(obj.constructor === Array) // true
四、最稳妥方案:Array.isArray()
Array.isArray()
检查传递的值是否为Array
。它不检查值的原型链,也不依赖于它所附加的Array
构造函数。对于使用数组字面量语法或Array
构造函数创建的任何值,它都会返回true
。这使得它可以安全地使用跨领域(cross-realm)对象,其中Array
构造函数的标识是不同的,因此会导致instanceof Array
失败。
Mdn
中对Array.isArray()
方法的描述非常清楚了,既不检查值的原型链,也不依赖于Array
构造函数,并且还能跨领域,也就是解决了我们上面iframe
例子中的问题。
javascript
console.log(Array.isArray([])); // true
const obj1 = {
[Symbol.toStringTag]: "Array",
};
console.log(Array.isArray(obj1)); // false
const obj2 = {};
Object.setPrototypeOf(obj2, Array.prototype);
console.log(Array.isArray(obj2)); // false
const frame = document.querySelector("iframe");
const frameArray = frame.contentWindow.Array;
const arr = new frameArray(1, 2, 3);
console.log(Array.isArray(arr)); //true
冷知识
还有一个冷知识:其实 Array.prototype 也是一个数组
javascript
Array.isArray(Array.prototype); // true
兼容性
可以看到,除了少量低版本浏览器和环境,目前在开发中使用这个API来判断是否为数组是基本没有问题的。