作为前端开发者,你大概率在开发中遇到过「类数组」——它长得像数组,却不能直接用数组的方法,比如forEach、map,稍不注意就会踩坑。今天我们就彻底拆解JS中的类数组,从概念、常见场景到转换方法,一次性讲明白。
一、什么是类数组(Array-like)?
类数组,顾名思义是「看起来像数组但不是数组」的对象,核心特征有两个:
1. 拥有length属性(表示元素个数);
2. 可以通过数字索引(如0、1)访问元素;
3. 不具备数组原型(Array.prototype)上的方法(如push、forEach、map)。
简单来说:类数组是「长得像数组的普通对象」,本质还是对象,只是结构模仿了数组。
二、常见的类数组实例
光说概念太抽象,我们看几个开发中最常遇到的类数组:
1. arguments对象(函数内置)
arguments是函数内部的内置对象,包含了调用函数时传入的所有参数,是最经典的类数组。
function sum() {
console.log(arguments); // 输出:[1, 2, 3](看起来像数组)
console.log(typeof arguments); // 输出:object(本质是对象)
console.log(arguments instanceof Array); // 输出:false(不是数组)
// 尝试用数组方法(报错)
// arguments.forEach(item => console.log(item)); // Uncaught TypeError: arguments.forEach is not a function
// 可以通过索引和length访问
console.log(arguments[0]); // 输出:1
console.log(arguments.length); // 输出:3
}
sum(1, 2, 3);
2. DOM集合(HTMLCollection/NodeList)
操作DOM时,document.getElementsByTagName()、document.getElementsByClassName()返回HTMLCollection,document.querySelectorAll()返回NodeList,这些都是类数组。
// 获取页面中所有的div
const divs = document.getElementsByTagName('div');
console.log(divs); // HTMLCollection(3) [div, div, div]
console.log(divs instanceof Array); // false
console.log(divs.length); // 3(有length属性)
console.log(divs[0]); // 第一个div元素
// 尝试用数组方法(报错)
// divs.map(div => div.style.color = 'red'); // Uncaught TypeError: divs.map is not a function
3. 自定义类数组对象
我们也可以手动创建符合「索引+length」特征的类数组:
const arrayLike = {
0: '苹果',
1: '香蕉',
2: '橙子',
length: 3 // 必须有length属性,且值与索引数量匹配
};
console.log(arrayLike[1]); // 输出:香蕉
console.log(arrayLike.length); // 输出:3
console.log(arrayLike instanceof Array); // 输出:false
三、类数组转普通数组的4种常用方法
类数组不能直接用数组方法,所以开发中常需要把它转换成真正的数组。下面是4种最实用的方法,按「推荐度+兼容性」排序:
方法1:Array.from()(ES6,推荐)
Array.from()是ES6专门为「类数组/可迭代对象」设计的转换方法,语法简洁、语义清晰,是首选方案。
语法:Array.from(类数组对象)
// 示例1:转换arguments
function sum() {
const args = Array.from(arguments);
console.log(args instanceof Array); // true
return args.reduce((total, num) => total + num, 0); // 用数组reduce求和
}
console.log(sum(1, 2, 3)); // 输出:6
// 示例2:转换DOM集合
const divs = document.getElementsByTagName('div');
const divArray = Array.from(divs);
divArray.forEach(div => div.style.color = 'red'); // 成功执行
// 示例3:转换自定义类数组
const arrayLike = {0: 'a', 1: 'b', length: 2};
const arr = Array.from(arrayLike);
console.log(arr); // 输出:['a', 'b']
方法2:扩展运算符(…)(ES6)
扩展运算符...可以将「可迭代的类数组」(如arguments、NodeList)展开成数组,语法更简洁,但注意:仅支持可迭代对象(纯自定义类数组可能不生效)。
// 转换arguments
function fn() {
const args = [...arguments];
console.log(args); // [10, 20, 30]
console.log(args instanceof Array); // true
}
fn(10, 20, 30);
// 转换NodeList(querySelectorAll返回的是可迭代的)
const lis = document.querySelectorAll('li');
const liArray = [...lis];
liArray.map(li => console.log(li.textContent));
// 注意:纯自定义类数组(不可迭代)无法用扩展运算符
const arrayLike = {0: 'x', 1: 'y', length: 2};
// const arr = [...arrayLike]; // Uncaught TypeError: arrayLike is not iterable
方法3:Array.prototype.slice.call()(兼容旧环境)
这是ES6之前的经典方法,利用数组的slice方法(不传参数时返回原数组的拷贝),通过call改变this指向类数组,从而实现转换。兼容性最好(支持IE)。
// 转换arguments
function fn() {
const args = Array.prototype.slice.call(arguments);
console.log(args instanceof Array); // true
}
fn(1, 2);
// 转换DOM集合
const divs = document.getElementsByClassName('box');
const divArray = Array.prototype.slice.call(divs);
// 转换自定义类数组
const arrayLike = {0: 100, 1: 200, length: 2};
const arr = Array.prototype.slice.call(arrayLike);
console.log(arr); // [100, 200]
方法4:Array.prototype.concat.apply()(备用)
利用concat的拼接特性,apply接收类数组作为参数,最终返回新数组(不常用,了解即可)。
const arrayLike = {0: 'a', 1: 'b', length: 2};
const arr = Array.prototype.concat.apply([], arrayLike);
console.log(arr); // ['a', 'b']
四、总结
核心要点回顾
- 类数组本质:是拥有
length和数字索引的普通对象,不是数组,没有数组原型方法; - 常见场景:
arguments、DOM集合(HTMLCollection/NodeList)、自定义索引+length的对象; - 转换方法:
- 优先用
Array.from()(ES6,通用); - 简洁场景用扩展运算符
...(仅支持可迭代类数组); - 旧环境兼容用
Array.prototype.slice.call(); - 备用方案:
Array.prototype.concat.apply()。
- 优先用
掌握类数组的概念和转换方法,能避免开发中「明明看起来是数组却调不了方法」的坑,尤其是在处理函数参数、DOM集合时,会让你的代码更健壮~




