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 分钟前
很全面的前端面试题——计算机网络篇(上)
前端·网络协议·面试
JSON_L4 分钟前
Vue 详情模块 3
前端·javascript·vue.js
哀木14 分钟前
关于 one drive 上传功能的前端接入(未完成)
前端
flashlight_hi31 分钟前
LeetCode 分类刷题:2824. 统计和小于目标的下标对数目
javascript·数据结构·算法·leetcode
大熊学员1 小时前
HTML与JavaScript的羁绊
前端·css·html
Mike_Wuzy1 小时前
【前端】CSS基础知识及基本应用
前端·css
啃火龙果的兔子1 小时前
WebView 中控制光标
前端
流星先生!1 小时前
前端小数点处理
开发语言·前端·javascript
不是二师兄的八戒1 小时前
PDF转图片工具技术文档(命令行版本)
前端·python·pdf
拾光拾趣录2 小时前
🔥9道题穿透JS底层:堆栈/异步/执行栈连环问,第5题99%人翻车?📉
前端·面试