MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript类数组对象的转换与使用

2023-02-194.7k 阅读

JavaScript类数组对象的概念

在JavaScript中,类数组对象(array - like object)是一种类似数组的数据结构,但它并不是真正的数组。它拥有一个 length 属性,并且可以通过数字索引访问其元素,然而却不具备数组所特有的方法,比如 pushpopmap 等。

常见的类数组对象有函数内部的 arguments 对象,它包含了传递给函数的所有参数。另外,通过 document.querySelectorAll 获取的DOM元素集合也是类数组对象。例如:

function example() {
    console.log(arguments);
}
example(1, 2, 3);

上述代码中,在函数 example 内部,arguments 就是一个类数组对象,它有 length 属性,并且可以像数组一样通过索引访问其元素,如 arguments[0] 就能获取到第一个参数 1

类数组对象与真正数组的区别

  1. 原型链与方法:真正的数组继承自 Array.prototype,拥有一系列数组独有的方法,如 forEachfilterreduce 等。而类数组对象并不继承自 Array.prototype,所以不能直接调用这些方法。例如:
let arr = [1, 2, 3];
let likeArr = {0: 1, 1: 2, 2: 3, length: 3};

console.log(arr.forEach); 
console.log(likeArr.forEach); 

在上述代码中,arr.forEach 会输出一个函数,因为数组有 forEach 方法;而 likeArr.forEach 会输出 undefined,因为类数组对象没有这个方法。

  1. 数据类型:使用 Array.isArray 方法可以判断一个对象是否为真正的数组。该方法对于类数组对象会返回 false。例如:
let arr = [1, 2, 3];
let likeArr = {0: 1, 1: 2, 2: 3, length: 3};

console.log(Array.isArray(arr)); 
console.log(Array.isArray(likeArr)); 

这里,Array.isArray(arr) 输出 trueArray.isArray(likeArr) 输出 false

类数组对象转换为数组的方法

  1. 使用 Array.from()Array.from() 方法是将类数组对象转换为真正数组的一种便捷方式。它可以接受一个类数组对象作为第一个参数,还可以接受一个映射函数作为第二个参数,类似于数组的 map 方法,对转换后的数组元素进行处理。例如:
let likeArr = {0: 1, 1: 2, 2: 3, length: 3};
let arr = Array.from(likeArr);
console.log(arr); 

// 使用映射函数
let newArr = Array.from(likeArr, num => num * 2);
console.log(newArr); 

在上述代码中,首先通过 Array.from(likeArr) 将类数组对象 likeArr 转换为数组 arr。然后,使用映射函数 num => num * 2,将 likeArr 中的每个元素乘以2后生成新的数组 newArr

  1. 使用 [...spread] 语法:ES6引入的展开语法(spread syntax)也可以将类数组对象转换为数组。例如:
function example() {
    let arr = [...arguments];
    console.log(arr); 
}
example(1, 2, 3);

在函数 example 内部,通过 [...arguments] 将类数组对象 arguments 转换为真正的数组 arr。展开语法的本质是将类数组对象的元素逐一展开,然后构建成一个新的数组。

  1. 使用 Array.prototype.slice.call():在ES6之前,常用 Array.prototype.slice.call() 方法来将类数组对象转换为数组。slice 方法本身是数组的方法,用于截取数组的一部分并返回一个新数组。通过 call 方法改变 slice 方法的 this 指向,使其作用于类数组对象。例如:
let likeArr = {0: 1, 1: 2, 2: 3, length: 3};
let arr = Array.prototype.slice.call(likeArr);
console.log(arr); 

这里,Array.prototype.slice.call(likeArr) 将类数组对象 likeArr 转换为数组 arrcall 方法的第一个参数为要改变的 this 指向,这里是类数组对象 likeArr,这样 slice 方法就会作用于 likeArr,从而实现转换。

转换后数组的使用

  1. 使用数组方法:一旦将类数组对象转换为数组,就可以使用数组的各种方法。例如,使用 map 方法对数组元素进行操作:
function example() {
    let arr = [...arguments];
    let newArr = arr.map(num => num * 2);
    console.log(newArr); 
}
example(1, 2, 3);

在上述代码中,先将 arguments 转换为数组 arr,然后使用 map 方法将 arr 中的每个元素乘以2,生成新的数组 newArr

  1. 与其他数组交互:转换后的数组可以与其他数组进行合并、比较等操作。例如,使用 concat 方法合并数组:
let likeArr = {0: 1, 1: 2, 2: 3, length: 3};
let arr1 = Array.from(likeArr);
let arr2 = [4, 5, 6];
let combinedArr = arr1.concat(arr2);
console.log(combinedArr); 

这里,先将类数组对象 likeArr 转换为数组 arr1,然后使用 concat 方法将 arr1arr2 合并成 combinedArr

不转换直接使用类数组对象的场景与方法

  1. 遍历类数组对象:虽然类数组对象没有 forEach 方法,但可以通过传统的 for 循环来遍历。例如:
function example() {
    for (let i = 0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}
example(1, 2, 3);

在上述代码中,通过 for 循环遍历 arguments 类数组对象,输出其中的每个元素。

  1. 使用 Array.prototype.forEach.call():类似于 Array.prototype.slice.call() 的思路,可以使用 Array.prototype.forEach.call() 来对类数组对象进行遍历。例如:
let likeArr = {0: 1, 1: 2, 2: 3, length: 3};
Array.prototype.forEach.call(likeArr, (value, index) => {
    console.log(`Index: ${index}, Value: ${value}`);
});

这里,通过 Array.prototype.forEach.call(likeArr, callback),将 forEach 方法的 this 指向类数组对象 likeArr,从而实现对 likeArr 的遍历,callback 函数中的 value 是当前元素,index 是当前索引。

  1. 在特定API中直接使用:有些JavaScript的API本身就支持类数组对象作为参数。例如,Math.max.apply(null, arguments) 可以在函数内部找到传递进来的最大参数值。apply 方法的第一个参数为 null,表示不指定 this 指向,第二个参数为类数组对象 arguments。例如:
function findMax() {
    let max = Math.max.apply(null, arguments);
    console.log(max);
}
findMax(1, 5, 3);

在上述代码中,Math.max.apply(null, arguments) 会在 arguments 类数组对象中找到最大值并输出。

注意事项

  1. 稀疏类数组对象:如果类数组对象是稀疏的(即存在未初始化的索引位置),在转换为数组时可能会有不同的表现。例如:
let sparseLikeArr = {0: 1, 2: 3, length: 3};
let arr1 = Array.from(sparseLikeArr);
let arr2 = [...sparseLikeArr];

console.log(arr1); 
console.log(arr2); 

在上述代码中,Array.from(sparseLikeArr) 会将未初始化的索引位置填充为 undefined,而展开语法 [...sparseLikeArr] 会忽略未初始化的索引位置。

  1. 性能问题:在选择转换方法时,需要考虑性能。一般来说,[...spread] 语法在现代浏览器中性能较好,而 Array.prototype.slice.call() 在旧版本浏览器中兼容性更好,但性能相对较差。在性能敏感的场景下,建议进行性能测试后选择合适的方法。

  2. 数据类型兼容性:某些类数组对象可能包含非标准的数据类型,在转换和使用过程中需要注意兼容性。例如,DOM元素集合中的元素是复杂的DOM节点对象,在进行操作时要遵循DOM操作的规范。

应用场景案例

  1. 函数参数处理:在编写可接受不定数量参数的函数时,常常会用到类数组对象转换为数组。例如,编写一个函数来计算多个数字的平均值:
function calculateAverage() {
    let arr = Array.from(arguments);
    let sum = arr.reduce((acc, num) => acc + num, 0);
    return sum / arr.length;
}
let average = calculateAverage(1, 2, 3, 4);
console.log(average); 

在上述代码中,先将 arguments 转换为数组 arr,然后使用 reduce 方法计算数组元素的总和,最后计算平均值。

  1. DOM操作:当使用 document.querySelectorAll 获取到类数组的DOM元素集合后,可以将其转换为数组,方便进行批量操作。例如,为页面上所有的 p 元素添加点击事件:
let pElements = document.querySelectorAll('p');
let pArray = Array.from(pElements);
pArray.forEach(p => {
    p.addEventListener('click', () => {
        console.log('Paragraph clicked!');
    });
});

这里,先将 document.querySelectorAll('p') 返回的类数组对象转换为数组 pArray,然后使用 forEach 方法为每个 p 元素添加点击事件。

  1. 模拟栈和队列操作:虽然类数组对象本身不具备栈和队列的方法,但转换为数组后就可以轻松实现。例如,模拟栈的 pushpop 操作:
function stackExample() {
    let stack = [];
    function pushToStack() {
        let args = Array.from(arguments);
        stack.push(...args);
    }
    function popFromStack() {
        return stack.pop();
    }

    pushToStack(1, 2, 3);
    console.log(popFromStack()); 
    console.log(popFromStack()); 
}
stackExample();

在上述代码中,pushToStack 函数将传入的参数转换为数组后添加到 stack 数组中,模拟栈的 push 操作;popFromStack 函数直接使用数组的 pop 方法模拟栈的 pop 操作。

通过深入理解JavaScript类数组对象的转换与使用,可以让开发者更加灵活地处理各种数据结构,提高代码的效率和可读性。无论是在函数参数处理、DOM操作还是其他复杂的编程场景中,掌握这些知识都能为开发工作带来便利。同时,需要注意不同转换方法的特点和适用场景,以及在操作类数组对象时的性能和兼容性问题。