JavaScript 类数组:披着数组外衣的 “伪装者”?

前言

在JavaScript开发中,我们经常会遇到一些看起来像数组,但实际上并不是真正数组的对象,这些对象被称为 "类数组对象(Array-like Objects)"。接下来我就将带大家认识一下类数组对象。

一、什么是类数组对象?

类数组对象是指那些具有以下特征的对象:

    1. 拥有 length 属性,表示元素的数量
    1. 拥有索引元素(0, 1, 2...),可以通过索引访问
    1. 不具备数组原型上的方法(如 push, pop, forEach 等)

简单来说,类数组对象"看起来像"数组,但它们并不是从 Array 构造函数创建的实例,因此不能直接使用数组的方法。

二、常见的类数组对象

在JavaScript中,最常见的类数组对象包括:

1. DOM集合

DOM API返回的元素集合通常是类数组对象,例如:

javascript 复制代码
// 返回类数组对象
const divs = document.getElementsByTagName('div');
const paragraphs = document.querySelectorAll('p');

console.log(divs.length); // 正常工作
console.log(Array.isArray(divs)); // false
// divs.forEach(div => console.log(div)); // 错误!类数组对象没有forEach方法

2. 函数中的arguments对象

函数内部可以通过 arguments 对象访问所有传入的参数,它是一个类数组对象:

javascript 复制代码
function example() {
  console.log(arguments.length); // 参数数量
  console.log(arguments[0]);     // 第一个参数
  console.log(arguments[1]);     // 第二个参数
  
  // 但不能直接使用数组方法
  // arguments.forEach(arg => console.log(arg)); // 错误!
  
  console.log(Array.isArray(arguments)); // false
}

example('hello', 'world', 123);

3. 字符串

字符串在某种程度上也可以被视为类数组对象,因为它们有length属性,并且可以通过索引访问字符:

javascript 复制代码
const str = "Hello";
console.log(str.length); // 5
console.log(str[0]);     // "H"
console.log(str[1]);     // "e"

// 但字符串不是数组
console.log(Array.isArray(str)); // false

三、类数组对象与真正数组的区别

类数组对象与真正的数组有以下关键区别:

    1. 原型链不同 :类数组对象不继承自 Array.prototype,因此没有数组的内置方法
    1. 构造函数不同 :类数组对象不是由 Array() 构造函数创建的
    1. 行为差异:某些操作在类数组对象上的表现可能与数组不同

四、如何将类数组对象转换为真正的数组

有几种方法可以将类数组对象转换为真正的数组:

1. Array.from()

ES6引入的 Array.from() 方法是最直接的方式:

javascript 复制代码
function example() {
  const args = Array.from(arguments);
  // 现在args是真正的数组
  args.forEach(arg => console.log(arg)); // 正常工作
}

// DOM元素集合
const divs = document.getElementsByTagName('div');
const divsArray = Array.from(divs);
divsArray.forEach(div => console.log(div)); // 正常工作

2. 扩展运算符(Spread Operator)

ES6的扩展运算符也可以将类数组对象转换为数组:

javascript 复制代码
function example() {
  const args = [...arguments];
  // args是真正的数组
}

const paragraphs = document.querySelectorAll('p');
const paragraphsArray = [...paragraphs];

3. Array.prototype.slice.call()

在ES6之前,常用的方法是借用数组的slice方法:

javascript 复制代码
function example() {
  const args = Array.prototype.slice.call(arguments);
  // 或者使用简写形式
  // const args = [].slice.call(arguments);
}

const divs = document.getElementsByTagName('div');
const divsArray = Array.prototype.slice.call(divs);

五、自定义类数组对象

我们也可以创建自己的类数组对象:

javascript 复制代码
const myArrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

// 转换为真正的数组
const realArray = Array.from(myArrayLike);
console.log(realArray); // ['a', 'b', 'c']

六、在类数组对象上使用数组方法

虽然类数组对象不直接拥有数组方法,但我们可以通过函数的call或apply方法来借用数组的方法。(这种方法借用机制我在《少写重复代码的精髓:JS方法借用》这篇文章中进行了介绍,可以补补课)

javascript 复制代码
const arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

// 借用数组的join方法
const joined = Array.prototype.join.call(arrayLike, '-');
console.log(joined); // "a-b-c"

// 借用数组的map方法
const mapped = Array.prototype.map.call(arrayLike, item => item.toUpperCase());
console.log(mapped); // ["A", "B", "C"]

// 借用数组的filter方法
const filtered = Array.prototype.filter.call(arrayLike, item => item !== 'b');
console.log(filtered); // ["a", "c"]

七、类数组对象的实际应用场景

1. 函数参数处理

javascript 复制代码
function sum() {
  // 将arguments转换为数组并求和
  return Array.from(arguments).reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4)); // 10

2. DOM操作批处理

javascript 复制代码
// 为所有段落添加相同的类
const paragraphs = document.querySelectorAll('p');
Array.from(paragraphs).forEach(p => p.classList.add('highlight'));

// 或者使用借用方法
Array.prototype.forEach.call(paragraphs, p => p.classList.add('highlight'));

3. 字符串操作

javascript 复制代码
const str = "hello";
// 将字符串中的每个字符转换为大写
const chars = Array.from(str).map(char => char.toUpperCase());
console.log(chars.join('')); // "HELLO"

类数组对象的性能考虑

在处理大量元素时,类数组对象与真正的数组可能有性能差异:

    1. 原生数组方法通常针对数组进行了优化
    1. 将类数组对象转换为数组会产生额外的内存和计算开销
    1. 在某些情况下,直接在类数组对象上使用借用的数组方法可能比先转换再操作更高效

八、ES6+中的替代方案

随着JavaScript的发展,一些类数组对象的使用场景已经有了更好的替代方案:

    1. arguments对象 → 使用剩余参数(Rest Parameters)

      javascript 复制代码
      // 旧方式
      function oldWay() {
        const args = Array.from(arguments);
        // ...
      }
      
      // 新方式
      function newWay(...args) {
        // args已经是数组
        // ...
      }
    1. DOM集合 → 使用Array方法或迭代器

      javascript 复制代码
      // 现代浏览器中的NodeList已经实现了forEach方法
      document.querySelectorAll('div').forEach(div => {
        // 处理每个div
      });

结语

类数组对象是JavaScript中一个重要的概念,了解它们的特性和处理方法对于编写高效、可维护的代码至关重要。虽然ES6+提供了更便捷的方法来处理类数组对象,但在处理旧代码或特定场景时,了解类数组对象的本质仍然非常有价值。通过正确理解和使用类数组对象,我们可以更灵活地处理各种JavaScript编程场景。

希望这篇文章有帮助到你,如果文章有错误,请你在评论区指出,大家一起进步,谢谢🙏。

相关推荐
何双新2 小时前
Odoo AI 智能查询系统
前端·人工智能·python
无处不在的海贼8 小时前
小明的Java面试奇遇之发票系统相关深度实战挑战
java·经验分享·面试
秋名山大前端9 小时前
Chrome GPU 加速优化配置(前端 3D 可视化 / 数字孪生专用)
前端·chrome·3d
今天不要写bug9 小时前
antv x6实现封装拖拽流程图配置(适用于工单流程、审批流程应用场景)
前端·typescript·vue·流程图
luquinn9 小时前
实现统一门户登录跳转免登录
开发语言·前端·javascript
用户21411832636029 小时前
dify案例分享-5分钟搭建智能思维导图系统!Dify + MCP工具实战教程
前端
augenstern4169 小时前
HTML(面试)
前端
excel9 小时前
前端常见布局误区:1fr 为什么撑爆了我的容器?
前端
烛阴9 小时前
TypeScript 类型魔法:像遍历对象一样改造你的类型
前端·javascript·typescript
vayy9 小时前
uniapp中 ios端 scroll-view 组件内部子元素z-index失效问题
前端·ios·微信小程序·uni-app