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

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

相关推荐
DogDaoDao4 小时前
leetcode 面试经典 150 题:有效的括号
c++·算法·leetcode·面试··stack·有效的括号
桂月二二5 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
CodeClimb6 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
hunter2062066 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb6 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角6 小时前
CSS 颜色
前端·css
九酒7 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔7 小时前
HTML5 新表单属性详解
前端·html·html5
lee5768 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579658 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter