前言
在JavaScript开发中,我们经常会遇到一些看起来像数组,但实际上并不是真正数组的对象,这些对象被称为 “类数组对象(Array-like Objects)”。
一、什么是类数组对象?
类数组对象是指那些具有以下特征的对象:
-
-
拥有
length
属性,表示元素的数量 -
拥有索引元素(0, 1, 2…),可以通过索引访问
-
不具备数组原型上的方法(如
push
,pop
,forEach
等)
-
简单来说,类数组对象”看起来像”数组,但它们并不是从 Array
构造函数创建的实例,因此不能直接使用数组的方法。
二、常见的类数组对象
在JavaScript中,最常见的类数组对象包括:
1. DOM集合
DOM API返回的元素集合通常是类数组对象,例如:
1const divs = document.getElementsByTagName('div');
2const paragraphs = document.querySelectorAll('p');
3
4console.log(divs.length);
5console.log(Array.isArray(divs));
6
2. 函数中的arguments对象
函数内部可以通过 arguments
对象访问所有传入的参数,它是一个类数组对象:
1function example() {
2 console.log(arguments.length);
3 console.log(arguments[0]);
4 console.log(arguments[1]);
5
6
7
8
9 console.log(Array.isArray(arguments));
10}
11
12example('hello', 'world', 123);
3. 字符串
字符串在某种程度上也可以被视为类数组对象,因为它们有length属性,并且可以通过索引访问字符:
1const str = "Hello";
2console.log(str.length);
3console.log(str[0]);
4console.log(str[1]);
5
6
7console.log(Array.isArray(str));
三、类数组对象与真正数组的区别
类数组对象与真正的数组有以下关键区别:
-
-
原型链不同:类数组对象不继承自
Array.prototype
,因此没有数组的内置方法 -
构造函数不同:类数组对象不是由
Array()
构造函数创建的 -
行为差异:某些操作在类数组对象上的表现可能与数组不同
-
四、如何将类数组对象转换为真正的数组
有几种方法可以将类数组对象转换为真正的数组:
1. Array.from()
ES6引入的 Array.from()
方法是最直接的方式:
1function example() {
2 const args = Array.from(arguments);
3
4 args.forEach(arg => console.log(arg));
5}
6
7
8const divs = document.getElementsByTagName('div');
9const divsArray = Array.from(divs);
10divsArray.forEach(div => console.log(div));
2. 扩展运算符(Spread Operator)
ES6的扩展运算符也可以将类数组对象转换为数组:
1function example() {
2 const args = [...arguments];
3
4}
5
6const paragraphs = document.querySelectorAll('p');
7const paragraphsArray = [...paragraphs];
3. Array.prototype.slice.call()
在ES6之前,常用的方法是借用数组的slice方法:
1function example() {
2 const args = Array.prototype.slice.call(arguments);
3
4
5}
6
7const divs = document.getElementsByTagName('div');
8const divsArray = Array.prototype.slice.call(divs);
五、自定义类数组对象
我们也可以创建自己的类数组对象:
1const myArrayLike = {
2 0: 'a',
3 1: 'b',
4 2: 'c',
5 length: 3
6};
7
8
9const realArray = Array.from(myArrayLike);
10console.log(realArray);
六、在类数组对象上使用数组方法
虽然类数组对象不直接拥有数组方法,但我们可以通过函数的call或apply方法来借用数组的方法。(这种方法借用机制我在《少写重复代码的精髓:JS方法借用》这篇文章中进行了介绍,可以补补课)
1const arrayLike = {
2 0: 'a',
3 1: 'b',
4 2: 'c',
5 length: 3
6};
7
8
9const joined = Array.prototype.join.call(arrayLike, '-');
10console.log(joined);
11
12
13const mapped = Array.prototype.map.call(arrayLike, item => item.toUpperCase());
14console.log(mapped);
15
16
17const filtered = Array.prototype.filter.call(arrayLike, item => item !== 'b');
18console.log(filtered);
七、类数组对象的实际应用场景
1. 函数参数处理
1function sum() {
2
3 return Array.from(arguments).reduce((total, num) => total + num, 0);
4}
5
6console.log(sum(1, 2, 3, 4));
2. DOM操作批处理
1const paragraphs = document.querySelectorAll('p');
2Array.from(paragraphs).forEach(p => p.classList.add('highlight'));
3
4
5Array.prototype.forEach.call(paragraphs, p => p.classList.add('highlight'));
3. 字符串操作
1const str = "hello";
2
3const chars = Array.from(str).map(char => char.toUpperCase());
4console.log(chars.join(''));
类数组对象的性能考虑
在处理大量元素时,类数组对象与真正的数组可能有性能差异:
-
-
原生数组方法通常针对数组进行了优化
-
将类数组对象转换为数组会产生额外的内存和计算开销
-
在某些情况下,直接在类数组对象上使用借用的数组方法可能比先转换再操作更高效
-
八、ES6+中的替代方案
随着JavaScript的发展,一些类数组对象的使用场景已经有了更好的替代方案:
-
-
arguments对象 → 使用剩余参数(Rest Parameters)
1// 旧方式 2function oldWay() { 3 const args = Array.from(arguments); 4 // ... 5} 6 7// 新方式 8function newWay(...args) { 9 // args已经是数组 10 // ... 11}
-
DOM集合 → 使用Array方法或迭代器
1// 现代浏览器中的NodeList已经实现了forEach方法 2document.querySelectorAll('div').forEach(div => { 3 // 处理每个div 4});
-