JavaScript判断数据类型是Array还是Object的代码方法

作者:小括号

时间:2026-11-03

浏览:605

详细讲解在 JavaScript 中如何准确判断一个变量是 Array(数组)还是 Object(普通对象),深入剖析 typeof 与 instanceof 的局限,并提供 Array.isArray() 和 Object.prototype.toString.call() 等健壮实战代码。

文章目录
发布分类:前端开发 / JavaScript 基础与实战 • 阅读时间:约 6 分钟

在 JavaScript 日常业务开发与数据处理过程中,准确判断一个未知变量究竟是数组(Array)还是普通对象(Object)是极其高频的需求。最直观的答案是:判断数组请唯一推荐使用原生方法 Array.isArray(value);而判断普通键值对对象(Plain Object),最严谨的做法是调用 Object.prototype.toString.call(value) === '[object Object]'。本文将针对为什么常规 typeof 会在此失效展开解析,逐一演示主流判断方法的代码实现与边界限制,并提供一份开箱即用的健壮类型检测函数。

JavaScript判断数据类型是Array还是Object的代码方法
图 1:JavaScript 数组与普通对象类型判断核心方案全景图

一、为什么常规 typeof 无法区分 Array 与 Object?

初学者最常踩的一个坑是试图通过 typeof 区分数组与普通对象。然而在 JavaScript 中执行以下语句:

console.log(typeof []); // 输出: "object"
console.log(typeof {}); // 输出: "object"
console.log(typeof null); // 输出: "object"

可以清楚地看到,typeof [] 与 typeof {} 的返回值完全相同,均为 "object"。这是由 JavaScript 底层的设计机制决定的:

  • 在 ECMAScript 规范中,数组本质上就是一种具有特殊行为(例如自动维护 length 属性、数字索引下标等)的外来对象(Exotic Object)。
  • 在引擎底层的类型标签体系中,对象的类型标签均为 0,而 typeof 操作符只根据底层变量值的机器字标签判断,因此数组和所有普通对象都被直接归类为 "object"。
JavaScript判断数据类型是Array还是Object的代码方法
图 2:JavaScript 原型链继承结构与 typeof 判定机制原理图

正如图 2 所示,[] 的原型是 Array.prototype,而 Array.prototype 本身又继承自 Object.prototype。它们在原型链顶端汇合,这不仅导致 typeof 无法区分,还导致直接使用 value instanceof Object 时,数组与普通对象都会返回 true。

二、判断数组的首选方法:Array.isArray()

自 ECMAScript 5.1(ES5)开始,JavaScript 正式引入了 Array.isArray() 静态方法。这是目前官方最推荐、执行效率最高且语义最清晰的判断数组方式。

// 基础用法示例
const list = [1, 2, 3];
const mapObj = { name: "Tom", age: 18 };
const notArray = "string";

console.log(Array.isArray(list));     // 示例输出: true
console.log(Array.isArray(mapObj));   // 示例输出: false
console.log(Array.isArray(notArray)); // 示例输出: false
console.log(Array.isArray(null));     // 示例输出: false

Array.isArray() 相比其他方案具备两个无法替代的优势:

  1. 原生优化与简洁语义:无需手动借调原型方法,引擎内部做了专门的 C++ 级别判定,执行速度极快。
  2. 完美兼容多 Realm / iframe 跨窗口环境:在前端含有 <iframe>、弹窗或 Worker 等独立运行环境时,每个环境都拥有完全隔离的 Array 构造函数和全局原型对象。instanceof 会在此处彻底失效,而 Array.isArray() 依然能精准识别从子窗口传递过来的数组。
环境兼容性说明:所有现代浏览器以及 Node.js 全版本均已原生支持 Array.isArray()。如需运行在极老旧环境(如 IE8 及更早),可使用官方 polyfill:Array.isArray = Array.isArray || function(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; };。

三、精准区分 Array 与 Object 的终极方案:Object.prototype.toString.call()

如果业务不仅需要判断是否为数组,还需要严格确认一个变量是不是“真正的普通对象(Plain Object,即类似 {} 结构的键值对字典)”,那么最标准且健壮的方案是借用 Object.prototype.toString.call()。

// 提取内部真实类型标签
function getRawType(value) {
  return Object.prototype.toString.call(value);
}

console.log(getRawType([]));        // 示例输出: "[object Array]"
console.log(getRawType({}));        // 示例输出: "[object Object]"
console.log(getRawType(null));      // 示例输出: "[object Null]"
console.log(getRawType(new Date()));// 示例输出: "[object Date]"
console.log(getRawType(/abc/));     // 示例输出: "[object RegExp]"

每个对象内部在引擎层面都维护着一个内置属性 [[Class]](在 ES6 之后通过 Symbol.toStringTag 扩展)。当直接调用变量自身的 .toString() 时,数组或日期等对象通常重写了该方法(例如数组的 [1,2].toString() 输出 "1,2")。但通过 call 强行指定 Object.prototype 上最原始的 toString 方法时,引擎会按照规范读取内部类型标签,精准返回规范化的 "[object Type]" 字符串。

四、instanceof 运算符的适用场景与跨窗口陷阱

很多开发者也会尝试使用 instanceof 进行判断,比如 val instanceof Array 或 val instanceof Object。虽然在单一简单的页面中它看似可以工作,但存在两处致命隐患:

隐患一:原型链逐层溯源导致数组被误判为对象

const testArr = [10, 20];

console.log(testArr instanceof Array);  // 示例输出: true
console.log(testArr instanceof Object); // 示例输出: true (致命隐患!)

因为 Array 继承自 Object,所以 testArr instanceof Object 恒为 true。如果代码中直接通过 if (val instanceof Object) 来处理普通对象,数组也会被错误地进入该逻辑分支。必须先判断是否为数组并排除后,才能使用该操作符。

隐患二:跨 iframe(多全局 Realm)环境下失效

当页面嵌入了 <iframe> 时,子页面拥有自己独立的全局对象和构造函数:

JavaScript判断数据类型是Array还是Object的代码方法
图 3:跨 iframe / Realm 全局环境对比与 instanceof 失效机理
// 跨 iframe 环境实测场景说明
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const IFrameArray = iframe.contentWindow.Array;

// 在 iframe 中创建的数组
const crossArr = new IFrameArray(1, 2, 3);

// 主页面环境检测
console.log(crossArr instanceof Array);        // 示例输出: false (因为 Array !== IFrameArray)
console.log(Array.isArray(crossArr));          // 示例输出: true (安全正确!)
console.log(Object.prototype.toString.call(crossArr)); // 示例输出: "[object Array]" (安全正确!)

正如图 3 所示,crossArr 的隐式原型指向的是子页面的 IFrameArray.prototype,而当前主页面的 Array.prototype 位于不同的内存地址。因此 instanceof 会判定二者无继承关系返回 false。这也是在大型微前端或弹窗架构中严禁仅依赖 instanceof 校验数组的核心原因。

五、通过 constructor 属性判断的潜在风险

部分代码中会采用 val.constructor === Array 或 val.constructor === Object 进行对比。虽然看似直观,但在生产环境中存在两个明显隐患:

  • 空值抛出异常(TypeError):当 val 为 null 或 undefined 时,访问 val.constructor 会直接导致程序崩溃报错。
  • 无原型对象与属性篡改:通过 Object.create(null) 创建的纯净字典对象没有原型链,访问其 constructor 属性为 undefined;此外,对象的 constructor 属性是可以被轻易重写或篡改的,可靠性不足。

六、实战代码:编写健壮通用的类型鉴别工具函数

结合前面的分析,严密鉴别一个变量的数据类型的执行逻辑可归纳为如下决策链条:

JavaScript判断数据类型是Array还是Object的代码方法
图 4:JavaScript 数组与普通对象判定完整决策流程图

下面是一份经过严格工程验证的通用判断工具函数库,涵盖数组、普通对象(Plain Object)以及精确类型的提取:

/**
 * 健壮的类型判断实用工具集
 */
const DataType = {
  /**
   * 判断目标是否为数组
   * @param {*} value 待检测的值
   * @returns {boolean}
   */
  isArray(value) {
    return Array.isArray(value);
  },

  /**
   * 判断目标是否为纯普通对象 (Plain Object,如 {} 或 Object.create(null))
   * 排除 null、Array、Date、RegExp、Map 等其他特化对象
   * @param {*} value 待检测的值
   * @returns {boolean}
   */
  isPlainObject(value) {
    // 1. 过滤 null 及非 object 基础类型
    if (typeof value !== 'object' || value === null) {
      return false;
    }

    // 2. 严密比对 Object.prototype.toString
    if (Object.prototype.toString.call(value) !== '[object Object]') {
      return false;
    }

    // 3. 处理 Object.create(null) 纯净字典与普通原型对象
    const proto = Object.getPrototypeOf(value);
    return proto === null || proto === Object.prototype;
  },

  /**
   * 获取具体精确类型名称
   * @param {*} value 待检测的值
   * @returns {string} 如 'array', 'object', 'null', 'date', 'number' 等
   */
  getType(value) {
    if (value === null) return 'null';
    if (typeof value !== 'object') return typeof value;
    
    // 提取 [object Type] 中的 Type 并转小写
    const rawTag = Object.prototype.toString.call(value);
    const matches = rawTag.match(/\[object\s+(\w+)\]/);
    return matches ? matches[1].toLowerCase() : 'object';
  }
};

// ==================== 单元验证用例 ====================
console.log('--- 数组验证 ---');
console.log(DataType.isArray([1, 2]));          // 示例输出: true
console.log(DataType.isArray({ length: 2 }));    // 示例输出: false (类数组非真数组)
console.log(DataType.isArray('hello'));         // 示例输出: false

console.log('--- 纯对象验证 ---');
console.log(DataType.isPlainObject({ a: 1 }));      // 示例输出: true
console.log(DataType.isPlainObject(Object.create(null))); // 示例输出: true
console.log(DataType.isPlainObject([]));         // 示例输出: false
console.log(DataType.isPlainObject(null));       // 示例输出: false
console.log(DataType.isPlainObject(new Date())); // 示例输出: false

console.log('--- 通用类型获取 ---');
console.log(DataType.getType([]));              // 示例输出: "array"
console.log(DataType.getType({}));              // 示例输出: "object"
console.log(DataType.getType(new Set()));       // 示例输出: "set"

七、方法综合对比与技术选型建议

为了在团队协作和复杂项目架构中快速做出正确的技术选型,各方法的横向维度评估如下:

JavaScript判断数据类型是Array还是Object的代码方法
图 5:常见 JavaScript 类型判断方案特性综合对比矩阵
日常开发选型总结:
  1. 仅需判断是否为数组:毫无疑问直接采用 Array.isArray(val),语法精练,原生优化,抗跨域 iframe 风险。
  2. 需严格区分普通对象(字典):采用 Object.prototype.toString.call(val) === '[object Object]',或封装上述 DataType.isPlainObject(val),避免将 null、[] 或 Date 混入对象逻辑。
  3. 排除基本类型守卫:在函数入口处,可用 typeof val !== 'object' || val === null 快速拦截原始值,再交由上述两个专业方法处理。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章