人人都会AI编程

类数组对象与数组转换

更新时间:2026-07-11

在日常开发中,你经常会遇到一些长得像数组、但又不是真正数组的对象——它们有 length 属性,可以通过索引访问元素,却没有数组原生的那些强大方法(pushforEachmap 等)。这类对象统称为类数组对象(Array-like Object),理解它们并掌握转换技巧,能让你在处理 DOM 操作、函数参数等场景时游刃有余。

什么是类数组对象

类数组对象需要满足两个基本条件:

  1. 拥有 length 属性,且值为非负整数。
  2. 拥有若干索引属性(012……),范围在 0 ≤ index < length

除此之外,它的原型链上并没有 Array.prototype,因此无法直接调用数组方法

典型的类数组对象包括:

  • 函数的 arguments 对象
  • DOM 方法返回的节点列表(NodeList),如 document.querySelectorAll() 的返回值
  • 字符串(可用索引访问字符,有 length
  • 手动构造的普通对象,如 { 0: 'a', 1: 'b', length: 2 }
function demo() {
  console.log(arguments);        // Arguments(3) [1, 2, 3]
  console.log(arguments.length); // 3
  console.log(arguments[0]);     // 1
  // arguments.push(4);          // TypeError: arguments.push is not a function
}
demo(1, 2, 3);

const divs = document.querySelectorAll('div');
console.log(divs.length);        // 页面上的 div 数量
console.log(divs[0]);            // 第一个 div 元素
// divs.map(div => div.textContent); // TypeError: divs.map is not a function

字符串也算一种特殊的类数组:

const str = 'hello';
console.log(str.length); // 5
console.log(str[1]);     // 'e'

为什么要转为真正的数组

转为真正的数组后,你就能使用 mapfilterreduceforEachfind 等强大的数组方法,让数据处理变得简洁高效。这在以下场景尤为常见:

  • 对函数参数 arguments 进行遍历或转换(虽然现代开发中更推荐使用剩余参数 ...args
  • NodeList 进行批量操作,比如过滤出包含特定类的元素
  • 对字符串进行类似数组的处理(虽然直接用 split('') 也能转为数组)

转换方法一:Array.from()(最推荐)

ES6 引入了 Array.from(),能将类数组对象或可迭代对象直接转为数组,语义清晰,代码简洁,是目前最推荐的方式。

// 转换 arguments
function sum() {
  const args = Array.from(arguments);
  return args.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

// 转换 NodeList
const paragraphs = document.querySelectorAll('p');
const texts = Array.from(paragraphs, p => p.textContent);
// 第二个参数是可选的映射函数,相当于 .map()

// 转换字符串
const chars = Array.from('JavaScript');
console.log(chars); // ['J','a','v','a','S','c','r','i','p','t']

Array.from() 还能处理有 length 属性的任意对象,即使它没有索引值:

const fakeArray = { length: 3 };
console.log(Array.from(fakeArray)); // [undefined, undefined, undefined]

转换方法二:扩展运算符 ...(针对可迭代对象)

扩展运算符只能转换可迭代对象(即实现了 Symbol.iterator 方法的对象)。argumentsNodeList 都是可迭代的,但手动构造的纯对象(如 {0:'a',1:'b',length:2})不是可迭代对象,不能用扩展运算符。

// arguments 可迭代
function demo() {
  const args = [...arguments];
  console.log(args); // [1, 2, 3]
}
demo(1, 2, 3);

// NodeList 可迭代
const spans = document.querySelectorAll('span');
const spanArray = [...spans];

// 字符串可迭代
console.log([...'hi']); // ['h', 'i']

// 不可迭代的类数组会报错
const obj = { 0: 'a', 1: 'b', length: 2 };
// [...obj]; // TypeError: obj is not iterable

判断一个对象是否可迭代,可以检查是否存在 Symbol.iterator

typeof arguments[Symbol.iterator] === 'function'; // true
typeof document.querySelectorAll('div')[Symbol.iterator] === 'function'; // true

转换方法三:Array.prototype.slice.call()(传统方法)

在 ES6 之前,这是最常用的转换手段。它利用了 slice 方法在复制数组时,会遍历 length 和索引元素的特性。

function demo() {
  const args = Array.prototype.slice.call(arguments);
  // 或者用简写:const args = [].slice.call(arguments);
  console.log(args); // [1, 2, 3]
}
demo(1, 2, 3);

const links = document.getElementsByTagName('a');
const linkArray = [].slice.call(links);

这种方法适用于任何类数组对象(只要有 length 和索引),不受可迭代限制。但代码稍显晦涩,现代开发中更推荐 Array.from()

手动遍历转换(兼容性兜底)

如果你需要支持极古老的浏览器(如 IE8 以下),可以用一个简单的循环手动构建数组:

function toArray(arrayLike) {
  const arr = [];
  for (let i = 0; i < arrayLike.length; i++) {
    arr.push(arrayLike[i]);
  }
  return arr;
}

除非有特殊兼容需求,否则不推荐手写,直接用 Array.from() 即可。

注意事项与最佳实践

  1. Array.from() vs [...]
  • Array.from() 更强大:可以处理不可迭代的类数组,第二个参数能直接映射。
  • 扩展运算符语法更短,但仅适用于可迭代对象。
  • arguments 或者 NodeList 两者都可用,场景选择时优先考虑可读性和是否需要映射。
  1. 尽量不用 arguments

在 ES6+ 中,建议使用剩余参数 ...args 来替代 arguments,因为 ...args 直接得到一个真数组,无需转换,且箭头函数没有自己的 arguments 对象。

  1. NodeList 的实时性问题

使用 querySelectorAll 返回的 NodeList静态的(不会随 DOM 变化更新);但 getElementsByTagName 等返回的 HTMLCollection动态的。转换成数组后,两者都会变成固定快照,不用担心后续 DOM 变动影响已转换的数组。

  1. 字符串的特殊性

字符串既是类数组,又是可迭代对象。但需要注意,转换为数组后得到的是单个字符,对于包含表情符号或其他 Unicode 字符的字符串,Array.from 能正确处理代理对,而简单的 str.split('') 可能会出问题:

   const emoji = '😀👍';
   console.log(emoji.length);          // 4(因为每个表情是两个 UTF-16 码元)
   console.log(emoji.split(''));       // ['\uD83D', '\uDE00', '\uD83D', '\uDC4D'] 乱码
   console.log(Array.from(emoji));     // ['😀', '👍'] 正确
   

因此对字符串转换推荐使用 Array.from(str)

掌握类数组对象的本质和转换方法,能让你的数组操作代码更加统一、安全、易读,避免一堆丑陋的 for 循环或报错。这是前端工程师必备的基础技能之一。