一、什么是类数组对象
类数组对象(Array-like Object)是一种特殊的对象,具有以下特征:
- 有
length属性 - 表示元素个数 - 按索引存储数据 - 键名为数字(0, 1, 2...)
- 不具备数组方法 - 没有
push()、pop()、forEach()等方法
js
// 典型的类数组对象
const arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
// 但不是数组
console.log(Array.isArray(arrayLike)); // false
console.log(typeof arrayLike); // "object"
console.log(arrayLike.length); // 3
console.log(arrayLike[0]); // "a"
// console.log(arrayLike.push('d')); // 报错:arrayLike.push is not a function
二、常见的类数组对象
1. 函数的 arguments 对象
js
function example() {
console.log(arguments); // Arguments(3) [1, 2, 3]
console.log(Array.isArray(arguments)); // false
console.log(arguments.length); // 3
console.log(arguments[0]); // 1
}
example(1, 2, 3);
2. DOM 元素集合
js
// NodeList
const nodeList = document.querySelectorAll('div');
console.log(nodeList); // NodeList(3) [div, div, div]
console.log(Array.isArray(nodeList)); // false
// HTMLCollection
const htmlCollection = document.getElementsByTagName('div');
console.log(htmlCollection); // HTMLCollection(3)
3. 字符串
js
const str = 'hello';
console.log(str.length); // 5
console.log(str[0]); // 'h'
console.log(Array.isArray(str)); // false
4. TypedArray 相关对象
js
const buffer = new ArrayBuffer(16);
const int32View = new Int32Array(buffer);
// Int32Array 是真正的数组,但有些视图对象是类数组
三、类数组对象转数组的10种方法
方法1:Array.from() (ES6+,推荐)
js
const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
const arr1 = Array.from(arrayLike);
console.log(arr1); // ['a', 'b', 'c']
console.log(Array.isArray(arr1)); // true
// 可配合映射函数
const arr2 = Array.from(arrayLike, x => x.toUpperCase());
console.log(arr2); // ['A', 'B', 'C']
方法2:扩展运算符(...) (ES6+)
js
// 适用于可迭代的类数组对象
function example() {
const arr = [...arguments];
console.log(arr); // [1, 2, 3]
console.log(Array.isArray(arr)); // true
}
example(1, 2, 3);
// NodeList
const divs = [...document.querySelectorAll('div')];
console.log(Array.isArray(divs)); // true
方法3:Array.prototype.slice.call() (传统方法)
js
const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
const arr = Array.prototype.slice.call(arrayLike);
// 或简写为:
// const arr = [].slice.call(arrayLike);
console.log(arr); // ['a', 'b', 'c']
console.log(Array.isArray(arr)); // true
// arguments 示例
function sum() {
const args = Array.prototype.slice.call(arguments);
return args.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3)); // 6
方法4:Array.prototype.concat.apply()
js
const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
const arr = Array.prototype.concat.apply([], arrayLike);
console.log(arr); // ['a', 'b', 'c']
方法5:手动遍历转换
js
const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
// 方法1:for循环
const arr1 = [];
for (let i = 0; i < arrayLike.length; i++) {
arr1.push(arrayLike[i]);
}
console.log(arr1); // ['a', 'b', 'c']
// 方法2:Array.from的polyfill实现
function arrayFrom(arrayLike) {
const arr = [];
for (let i = 0; i < arrayLike.length; i++) {
arr[i] = arrayLike[i];
}
return arr;
}
console.log(arrayFrom(arrayLike)); // ['a', 'b', 'c']
方法6:使用 Object.values() (仅适用于键值为数字的情况)
js
const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
const arr = Object.values(arrayLike).filter(
(_, index) => index !== 'length' || !isNaN(index)
);
console.log(arr); // ['a', 'b', 'c', 3]
// 注意:这种方法可能包含length属性,需要过滤
方法7:使用 for...of 循环(要求对象可迭代)
js
// 字符串转数组
const str = 'hello';
const arr = [];
for (const char of str) {
arr.push(char);
}
console.log(arr); // ['h', 'e', 'l', 'l', 'o']
方法8:使用 Array.from 的映射能力
js
// 将类数组对象的属性名和值都转换
const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
// 转换键名和键值
const arr = Array.from({length: arrayLike.length}, (_, i) => ({
index: i,
value: arrayLike[i]
}));
console.log(arr);
// [
// {index: 0, value: 'a'},
// {index: 1, value: 'b'},
// {index: 2, value: 'c'}
// ]
方法9:使用 Array.prototype.splice.call()
js
const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
const arr = [];
Array.prototype.splice.call(arr, 0, 0, ...Object.values(arrayLike)
.filter((_, i) => i < arrayLike.length));
console.log(arr); // ['a', 'b', 'c']
方法10:利用 Generator 函数
js
function* arrayLikeToArray(arrayLike) {
for (let i = 0; i < arrayLike.length; i++) {
yield arrayLike[i];
}
}
const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
const arr = [...arrayLikeToArray(arrayLike)];
console.log(arr); // ['a', 'b', 'c']
四、各种转换方法对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
Array.from() |
简洁,ES6标准,支持映射 | 需要ES6+环境 | 现代浏览器/Node.js |
| 扩展运算符 | 简洁直观 | 要求对象可迭代 | 可迭代的类数组对象 |
slice.call() |
兼容性好 | 代码较长 | 需要兼容旧浏览器 |
| 手动遍历 | 完全可控 | 代码冗长 | 需要特殊处理时 |
Object.values() |
可获取所有值 | 需要过滤非数字键 | 简单的对象转换 |
五、实际应用示例
示例1:处理函数参数
js
// 旧方法
function sumOld() {
const args = Array.prototype.slice.call(arguments);
return args.reduce((a, b) => a + b, 0);
}
// 新方法
function sumNew(...args) { // 直接使用剩余参数
return args.reduce((a, b) => a + b, 0);
}
// 或
function sumFrom() {
return Array.from(arguments).reduce((a, b) => a + b, 0);
}
console.log(sumOld(1, 2, 3, 4)); // 10
console.log(sumNew(1, 2, 3, 4)); // 10
示例2:DOM操作
js
// 将NodeList转换为数组进行操作
const buttons = Array.from(document.querySelectorAll('button'));
// 或
// const buttons = [...document.querySelectorAll('button')];
buttons.forEach(button => {
button.addEventListener('click', () => {
console.log('Button clicked!');
});
});
// 使用数组方法
const redButtons = buttons.filter(btn => btn.classList.contains('red'));
const buttonTexts = buttons.map(btn => btn.textContent);
示例3:处理字符串
js
// 字符串转数组的多种方式
const str = 'hello';
const arr1 = Array.from(str); // ['h', 'e', 'l', 'l', 'o']
const arr2 = [...str]; // ['h', 'e', 'l', 'l', 'o']
const arr3 = str.split(''); // ['h', 'e', 'l', 'l', 'o']
const arr4 = Object.values(str); // ['h', 'e', 'l', 'l', 'o'] (但可能不是最佳选择)
// 反转字符串
const reversed = Array.from(str).reverse().join('');
console.log(reversed); // 'olleh'
六、注意事项
- length属性的重要性:类数组对象必须有正确的length属性
js
// 错误的length会导致问题
const badArrayLike = {0: 'a', 1: 'b', length: 1};
console.log(Array.from(badArrayLike)); // ['a'],第二个元素被忽略
- 稀疏数组处理:
js
const sparseArrayLike = {0: 'a', 2: 'c', length: 3};
const arr = Array.from(sparseArrayLike);
console.log(arr); // ['a', undefined, 'c']
- 性能考虑:对于大数据集,不同方法性能有差异
js
// 性能测试
const largeArrayLike = {length: 1000000};
for (let i = 0; i < 1000000; i++) {
largeArrayLike[i] = i;
}
// 测试不同转换方法的性能
console.time('Array.from');
const arr1 = Array.from(largeArrayLike);
console.timeEnd('Array.from');
console.time('slice.call');
const arr2 = Array.prototype.slice.call(largeArrayLike);
console.timeEnd('slice.call');
七、总结建议
- 现代开发 :优先使用
Array.from()或扩展运算符 - 兼容性要求 :使用
Array.prototype.slice.call() - NodeList/HTMLCollection :使用
Array.from()或扩展运算符 - arguments对象 :建议使用ES6的剩余参数语法
(...args) - 字符串转数组 :
Array.from(str)或[...str](支持Unicode字符)
选择方法时考虑:
- 代码可读性
- 浏览器兼容性
- 性能需求
- 是否需要映射/转换功能