类数组转化为数组的方法

详解在 JavaScript 中将类数组对象转换为真正数组的多种实现方式

问题

在 JavaScript 中,类数组对象(Array-like Object)是指具有 length 属性和索引元素,但不具备数组方法的对象。常见的类数组对象包括:

  • arguments 对象
  • DOM 方法返回的 NodeList
  • HTMLCollection
  • 字符串等

我们需要将这些类数组对象转换为真正的数组,以便使用数组的各种方法(如 mapfilterforEach 等)。

解答

// 方法1: Array.from()(ES6推荐)
function toArray1(arrayLike) {
  return Array.from(arrayLike);
}

// 方法2: 扩展运算符(ES6)
function toArray2(arrayLike) {
  return [...arrayLike];
}

// 方法3: Array.prototype.slice.call()
function toArray3(arrayLike) {
  return Array.prototype.slice.call(arrayLike);
}

// 方法4: [].slice.call()(简写形式)
function toArray4(arrayLike) {
  return [].slice.call(arrayLike);
}

// 方法5: Array.prototype.concat.apply()
function toArray5(arrayLike) {
  return Array.prototype.concat.apply([], arrayLike);
}

// 方法6: 手动遍历(兼容性最好)
function toArray6(arrayLike) {
  const arr = [];
  for (let i = 0; i < arrayLike.length; i++) {
    arr.push(arrayLike[i]);
  }
  return arr;
}

// 方法7: Array.prototype.map()
function toArray7(arrayLike) {
  return Array.prototype.map.call(arrayLike, item => item);
}

// 方法8: Array.of() + 扩展运算符
function toArray8(arrayLike) {
  return Array.of(...arrayLike);
}

使用示例

// 示例1: 转换 arguments 对象
function testArguments() {
  console.log('原始 arguments:', arguments);
  console.log('是否为数组:', Array.isArray(arguments)); // false
  
  const arr = Array.from(arguments);
  console.log('转换后:', arr);
  console.log('是否为数组:', Array.isArray(arr)); // true
  
  // 现在可以使用数组方法
  const doubled = arr.map(x => x * 2);
  console.log('使用 map 方法:', doubled);
}
testArguments(1, 2, 3, 4, 5);

// 示例2: 转换 NodeList
const divs = document.querySelectorAll('div');
console.log('NodeList:', divs);
console.log('是否为数组:', Array.isArray(divs)); // false

const divsArray = [...divs];
console.log('转换后的数组:', divsArray);
divsArray.forEach(div => {
  console.log(div.textContent);
});

// 示例3: 转换字符串
const str = 'hello';
const strArray = Array.from(str);
console.log('字符串转数组:', strArray); // ['h', 'e', 'l', 'l', 'o']

// 示例4: 转换自定义类数组对象
const arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};
console.log('类数组对象:', arrayLike);
const realArray = Array.from(arrayLike);
console.log('转换后:', realArray); // ['a', 'b', 'c']

// 示例5: Array.from() 的映射功能
const numbers = { 0: 1, 1: 2, 2: 3, length: 3 };
const doubled = Array.from(numbers, x => x * 2);
console.log('转换并映射:', doubled); // [2, 4, 6]

// 示例6: 性能对比测试
const largeArrayLike = { length: 10000 };
for (let i = 0; i < 10000; i++) {
  largeArrayLike[i] = i;
}

console.time('Array.from');
Array.from(largeArrayLike);
console.timeEnd('Array.from');

console.time('扩展运算符');
[...largeArrayLike];
console.timeEnd('扩展运算符');

console.time('slice.call');
Array.prototype.slice.call(largeArrayLike);
console.timeEnd('slice.call');

关键点

  • Array.from() 是最推荐的方法:语义清晰,功能强大,支持第二个参数进行映射操作,类似 map 方法

  • 扩展运算符简洁优雅:适用于具有迭代器接口的类数组对象,代码简洁,但不支持纯类数组对象(只有 length 和索引的对象)

  • slice.call() 兼容性好:适用于 ES5 环境,是传统的转换方式,兼容性最佳

  • 注意类数组对象的特征:必须具有 length 属性和数字索引,否则转换结果可能不符合预期

  • 性能考虑:对于大量数据,Array.from() 和扩展运算符性能较好,手动遍历在某些场景下可能更快

  • 迭代器协议:扩展运算符要求对象实现了迭代器协议(Symbol.iterator),而 Array.from() 既支持可迭代对象也支持类数组对象

  • 实际应用场景:在处理 DOM 操作、函数参数、第三方库返回的类数组对象时经常需要进行转换

  • ES6+ 环境优先使用Array.from() 或扩展运算符,代码更现代化和可读