避坑必备!新手开发者务必注意:这些常见的数组判断误区你知道吗?

一、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来判断是否为数组是基本没有问题的。

相关推荐
轻口味16 分钟前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王1 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发1 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀1 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪2 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef3 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6414 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻4 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云4 小时前
npm淘宝镜像
前端·npm·node.js