【原生JS】什么是类数组(Array-like Objects)最全详解

类数组(Array-like Objects)最全详解

类数组对象(Array-like objects)是指具有类似数组结构的对象,但它们并不是真正的 Array 实例。在 JavaScript 中,类数组对象通常具有以下特征:

  • 有索引属性(例如:0, 1, 2…)
  • 有一个 length 属性
  • 没有数组的方法(如 push(), slice() 等)

📚 一、什么是类数组?

类数组对象是模仿数组行为的对象,它具有数字索引和 length 属性,但不是 Array 的实例。

const arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

虽然看起来像数组,但它不能直接使用数组方法:

arrayLike.push('d'); // ❌ 报错:arrayLike.push is not a function

🧬 二、类数组的历史发展

类数组最早出现在早期的 DOM API 中,比如:

  • arguments 对象(函数内部自动创建)
  • NodeList(由 document.querySelectorAll() 返回)
  • HTMLCollection(由 document.getElementsByTagName() 返回)

这些对象设计为类数组是为了:

  • 性能优化:避免频繁创建数组对象。
  • 接口一致性:统一访问方式,不需要引入额外的数据结构。

🤔 三、为什么要有类数组?

  1. 性能考虑:类数组对象比数组更轻量,适合只读或静态数据集合。
  2. 历史原因:早期 JS 引擎不支持动态扩展对象,类数组是模拟数组的一种方式。
  3. DOM 接口设计:很多浏览器接口返回的是实时更新的集合(如 NodeList),使用类数组可以高效地反映 DOM 变化。

🌐 四、类数组是 JS 独有的吗?

不是,其他语言中也有类似的概念:

语言类似类数组的结构
Pythontuple, range, dict_keys/dict_values
JavaList, Map.Entry[]
C#IEnumerable, IEnumerator
PHPTraversable, ArrayAccess

JavaScript 的独特之处在于它的动态性和灵活性,使得类数组对象更容易被误用或混淆。


🆚 五、不同语言的类数组对比

特性JavaScriptPythonJavaC#
是否可变否(如 tuple是(如 List
是否内置类数组是(如 arguments, NodeList是(如 range()是(如 List是(如 IEnumerable
是否可迭代
是否支持数组方法❌(需转换)❌(需转换)✅(List✅(List

🔍 六、JS 类数组的特点

  1. 有索引和 length
  2. 不可使用数组方法
  3. 可转换为数组
  4. 有些是实时更新的(如 NodeList)

🧾 七、所有的 JS 类数组详解

✅1. arguments 对象

代码示例
function demo() {
  console.log(arguments); // 类数组对象,包含传入的所有参数
}
demo(1, 2, 3); // 输出: {0: 1, 1: 2, 2: 3, length: 3}
详细说明
  • 作用
    arguments 是函数内部自动生成的类数组对象,存储了调用函数时传入的所有参数。
  • 特点
    • 可通过数字索引(如 arguments[0])访问参数。
    • 具有 length 属性,表示参数个数。
    • 不是真正的数组,因此没有数组方法(如 mapfilter)。
  • 使用场景
    在需要处理不定数量参数的函数中(如实现 sum(...args))。
  • 转换为数组
    const args = Array.from(arguments); // 使用 Array.from
    const args = [...arguments];       // 使用展开运算符(ES6+)
    
注意事项
  • 箭头函数没有 arguments 对象,需使用剩余参数(...args)。
  • 现代代码中更推荐使用剩余参数语法:
    function demo(...args) {
      console.log(args); // 直接得到数组 [1, 2, 3]
    }
    

✅2. NodeList

代码示例
const nodes = document.querySelectorAll('div'); // 返回 NodeList
console.log(nodes); // 输出: NodeList(3) [div, div, div]
详细说明
  • 作用
    NodeList 是 DOM 操作返回的类数组对象,表示一组节点(如通过 querySelectorAll 获取的元素集合)。
  • 特点
    • 索引访问(如 nodes[0])。
    • 具有 length 属性。
    • 不是真正的数组,但现代浏览器可能支持 forEach 方法(非标准)。
  • 使用场景
    遍历 DOM 元素集合,进行批量操作(如修改样式、绑定事件)。
  • 转换为数组
    const nodeArray = Array.from(nodes); // 使用 Array.from
    const nodeArray = [...nodes];       // 使用展开运算符(ES6+)
    
注意事项
  • 某些 NodeList 是动态的(如 childNodes),会随 DOM 变化自动更新。

✅3. HTMLCollection

代码示例
const elements = document.getElementsByTagName('p'); // 返回 HTMLCollection
console.log(elements); // 输出: HTMLCollection(2) [p, p]
详细说明
  • 作用
    HTMLCollection 是 DOM 操作返回的类数组对象,表示一组元素(如通过 getElementsByTagName 获取的标签集合)。
  • 特点
    • 索引访问(如 elements[0])。
    • 具有 length 属性。
    • 不是真正的数组,且默认不支持数组方法。
    • 动态更新:当 DOM 结构变化时,HTMLCollection 会自动反映最新状态。
  • 使用场景
    动态操作一组元素(如实时统计页面中某类标签的数量)。
  • 转换为数组
    const elementArray = Array.from(elements); // 使用 Array.from
    
注意事项
  • NodeList 的区别:HTMLCollection 仅包含元素节点(如 <p>),而 NodeList 可能包含文本节点等。

✅4. 自定义类数组

代码示例
const myArrayLike = {
  0: 'foo',  // 数字索引属性
  1: 'bar',
  length: 2   // 必须定义 length
};
console.log(myArrayLike[0]); // 输出: 'foo'
详细说明
  • 作用
    模拟类数组结构,用于需要类似数组行为的场景(如实现自定义迭代器)。
  • 特点
    • 必须包含数字索引属性(如 01)和 length 属性。
    • 不是真正的数组,无法直接使用数组方法。
  • 使用场景
    Array.from 或展开运算符配合,转换为真正数组后操作。
  • 转换为数组
    const realArray = Array.from(myArrayLike); // 输出: ['foo', 'bar']
    
注意事项
  • 自定义类数组的键必须是连续的数字索引(如 01),否则 Array.from 可能无法正确解析。

总结

类数组类型来源动态性典型方法
arguments函数内部静态Array.from[...arguments]
NodeListDOM 查询(如 querySelectorAll通常静态Array.from[...nodes]
HTMLCollectionDOM 查询(如 getElementsByTagName动态更新Array.from
自定义类数组手动定义静态Array.from
关键点
  • 类数组的共性:索引访问 + length 属性。
  • 转换为数组:优先使用 Array.from(兼容性好)或展开运算符(ES6+)。
  • 性能考虑:频繁操作类数组时,转换为数组可提高效率。

⚠️ 类数组的注意事项

  1. 不能直接调用数组方法

    const arrLike = { 0: 'a', 1: 'b', length: 2 };
    arrLike.map(item => item.toUpperCase()); // ❌ TypeError
    
  2. 转换前应检查是否存在 length 和索引

    if (typeof obj.length === 'number' && !isNaN(obj.length)) {
      // 可以尝试转换为数组
    }
    
  3. 某些类数组是“伪数组”,不能用 slice 转换

    const arr = [].slice.call(arrayLike); // 旧式写法
    

✅ 八、类数组的最佳实践

1. 使用 Array.from()

const arr = Array.from(arrayLike);

2. 使用展开运算符 ...

const arr = [...arrayLike];

3. 使用 call / apply

const arr = Array.prototype.slice.call(arrayLike);

❓ 九、常见问题与解决方案

Q1: arguments 无法使用 map

错误示例:

arguments.map(item => item * 2); // ❌ TypeError

正确做法:

const args = Array.from(arguments);
const doubled = args.map(item => item * 2);

Q2: NodeList 不能用 forEach

错误示例:

document.querySelectorAll('div').forEach(el => el.style.color = 'red'); // ❌

正确做法:

Array.from(document.querySelectorAll('div')).forEach(el => {
  el.style.color = 'red';
});

Q3: 类数组没有 includes

错误示例:

arrayLike.includes('value'); // ❌

正确做法:

Array.from(arrayLike).includes('value');

🖼️ 十、框架中的类数组应用

Vue

Vue 中的 $listeners$attrs 等属性本质上是对象,但有时需要处理为数组:

<template>
  <input v-bind="$attrs" />
</template>

<script>
export default {
  inheritAttrs: false
}
</script>

React

React 中的 children 是一个类数组对象(特别是多子节点时):

function Parent({ children }) {
  const childArray = React.Children.toArray(children);
  return <div>{childArray}</div>;
}

jQuery

jQuery 的选择器返回的是类数组:

const $els = $('div');
$els.each(function() {
  $(this).css('color', 'blue');
});

Angular

Angular 中的 QueryList 是类数组结构:

@ViewChildren('items') items!: QueryList<ElementRef>;
ngAfterViewInit() {
  this.items.forEach(item => {
    item.nativeElement.style.color = 'green';
  });
}

🔌 十一、插件中的类数组应用

1. Lodash

Lodash 的 _.each 支持类数组:

_.each(arrayLike, function(value, index) {
  console.log(value);
});

2. Axios

Axios 的拦截器可能返回类数组结构(如响应头):

axios.get('/user').then(response => {
  console.log(response.headers); // 类数组
});

🧩 十二、总结

类型是否可迭代是否可转换为数组是否可修改
arguments
NodeList
HTMLCollection
自定义类数组✅(取决于实现)✅(取决于实现)

🧪 类数组示例代码汇总

// arguments 类数组
function demoArgs() {
  console.log(Array.isArray(arguments)); // false
  const args = Array.from(arguments);
  console.log(args); // [1, 2, 3]
}
demoArgs(1, 2, 3);

// NodeList 类数组
const nodeList = document.querySelectorAll('div');
const divs = Array.from(nodeList);
divs.forEach(div => div.style.color = 'red');

// HTMLCollection 类数组
const htmlCollection = document.getElementsByTagName('p');
const paras = [...htmlCollection];

// 自定义类数组
const customLike = {
  0: 'apple',
  1: 'banana',
  length: 2
};

const fruits = Array.from(customLike);
console.log(fruits); // ['apple', 'banana']

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈前端老曹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值