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

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

相关推荐
excel6 分钟前
webpack 核心编译器 十四 节
前端
excel13 分钟前
webpack 核心编译器 十三 节
前端
腾讯TNTWeb前端团队7 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
uhakadotcom10 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰11 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪11 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪11 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy11 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom12 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom12 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试