一篇文章带你了解类数组与数组的区别

174 阅读9分钟

js的普通数组

在JavaScript中,普通数组(也称为原生数组或标准数组)是通过Array构造函数或数组字面量语法创建的。这些数组拥有Array.prototype上的所有方法和属性,使得它们能够方便地进行各种数组操作。

构造函数构造数组,普通数组的构造函数是Array

let array = new Array(1, 2, 3);

对象字面量构造数组

let array = [1, 2, 3];

我们来聊聊普通数组常用方法

可变方法

这些方法会直接修改原数组。

  1. push() :向数组的末尾添加一个或多个元素,并返回新的数组长度。
  2. pop() :移除数组的最后一个元素,并返回该元素(数组长度减 1)。
  3. shift() :移除数组的第一个元素,并返回该元素(数组长度减 1),同时数组中的其他元素前移一位。
  4. unshift() :在数组的开头添加一个或多个元素,并返回新的数组长度,同时原数组中的元素相应后移。
  5. splice() :通过删除或替换现有元素或添加新元素来修改数组,并返回被删除的元素数组。
  6. sort() :对数组的元素进行排序,并返回数组。默认情况下,元素会被转换为字符串并按照字典序进行排序。
  7. reverse() :反转数组中的元素顺序,并返回数组。

不可变方法

这些方法不会修改原数组,而是返回一个新的数组。

  1. slice() :返回一个新的数组对象,这一对象是一个浅拷贝,包含从 begin(包括 begin)到 end(不包括 end)的原数组中的部分元素。原数组不会被修改。
  2. concat() :用于合并两个或多个数组。此方法不会改变现有数组,而是返回一个新数组。
  3. join() :将数组的所有元素连接成一个字符串,元素之间用指定的分隔符进行分隔。如果不提供分隔符,则默认使用逗号(,)作为分隔符。
  4. toString() :返回数组的字符串表示,该字符串由数组中的每个元素的 toString() 返回值经调用 join 方法连接(以逗号分隔)而成。
  5. indexOf() :返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回 -1。
  6. lastIndexOf() :返回在数组中可以找到一个给定元素的最后一个索引,如果不存在,则返回 -1。
  7. every() :测试数组的所有元素是否都通过了被提供的测试函数。它返回一个布尔值。
  8. some() :测试数组中是否至少有一个元素通过了被提供的测试函数。它返回一个布尔值。
  9. filter() :创建一个新数组,其包含通过所提供函数实现的测试的所有元素。
  10. map() :创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
  11. reduce()  和 reduceRight() :对数组中的每个元素(从左到右或从右到左)执行一个由您提供的 reducer 函数(升序或降序执行),将其结果汇总为单个返回值。
  12. forEach() :对数组的每个元素执行一次提供的函数。

js的类数组

类数组对象(Array-like objects)是指那些具有 length 属性和索引属性的对象,但它们并不是真正的数组。常见的类数组对象包括 arguments 对象、DOM 方法返回的 NodeList 和 HTMLCollection 等。尽管这些对象看起来像数组,但它们并没有数组的所有方法。为了在类数组对象上使用数组的方法,通常需要将它们转换为真正的数组。

以下是一些常用的数组方法,以及如何在类数组对象上使用这些方法:

1. Array.prototype.slice.call()

  • 用途:将类数组对象转换为真正的数组。
  • 示例
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const array = Array.prototype.slice.call(arrayLike);
    console.log(array); // 输出: ['a', 'b', 'c']
    

2. Array.from()

  • 用途:从类数组或可迭代对象创建一个新的数组实例。
  • 示例
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const array = Array.from(arrayLike);
    console.log(array); // 输出: ['a', 'b', 'c']
    

3. Array.prototype.forEach.call()

  • 用途:遍历类数组对象中的每个元素。
  • 示例
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    Array.prototype.forEach.call(arrayLike, (item, index) => {
        console.log(`Index: ${index}, Value: ${item}`);
    });
    // 输出:
    // Index: 0, Value: a
    // Index: 1, Value: b
    // Index: 2, Value: c
    

4. Array.prototype.map.call()

  • 用途:对类数组对象中的每个元素应用一个函数,并返回一个新的数组。
  • 示例
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const newArray = Array.prototype.map.call(arrayLike, (item, index) => {
        return item.toUpperCase();
    });
    console.log(newArray); // 输出: ['A', 'B', 'C']
    

5. Array.prototype.filter.call()

  • 用途:过滤类数组对象中的元素,返回一个新的数组。
  • 示例
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const filteredArray = Array.prototype.filter.call(arrayLike, (item, index) => {
        return item > 'a';
    });
    console.log(filteredArray); // 输出: ['b', 'c']
    

6. Array.prototype.reduce.call()

  • 用途:对类数组对象中的每个元素执行一个累加器函数,将其结果汇总为单个值。
  • 示例
    const arrayLike = { 0: 1, 1: 2, 2: 3, length: 3 };
    const sum = Array.prototype.reduce.call(arrayLike, (acc, item) => {
        return acc + item;
    }, 0);
    console.log(sum); // 输出: 6
    

7. Array.prototype.find.call()

  • 用途:查找类数组对象中满足条件的第一个元素。
  • 示例
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const foundItem = Array.prototype.find.call(arrayLike, (item) => {
        return item === 'b';
    });
    console.log(foundItem); // 输出: 'b'
    

8. Array.prototype.findIndex.call()

  • 用途:查找类数组对象中满足条件的第一个元素的索引。
  • 示例
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const foundIndex = Array.prototype.findIndex.call(arrayLike, (item) => {
        return item === 'b';
    });
    console.log(foundIndex); // 输出: 1
    

9. Array.prototype.some.call()

  • 用途:检查类数组对象中是否有至少一个元素满足条件。
  • 示例
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const hasB = Array.prototype.some.call(arrayLike, (item) => {
        return item === 'b';
    });
    console.log(hasB); // 输出: true
    

10. Array.prototype.every.call()

  • 用途:检查类数组对象中的所有元素是否都满足条件。
  • 示例
    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const allLowercase = Array.prototype.every.call(arrayLike, (item) => {
        return item === item.toLowerCase();
    });
    console.log(allLowercase); // 输出: true
    

我们来聊聊类数组常用的数组对象

类数组(Array-like objects)是指那些具有 length 属性和索引属性的对象,但它们并不是真正的 JavaScript 数组。这些对象看起来像数组,因为它们可以通过索引来访问元素,并且有一个 length 属性来表示元素的数量,但它们缺少数组的内置方法(如 push, pop, map, filter 等)。

常见的类数组对象

  1. arguments 对象

    • 在函数内部,arguments 是一个类数组对象,它包含了传递给函数的所有参数。
    function example() {
        console.log(arguments); // 类数组对象
        console.log(arguments.length); // 参数数量
        console.log(arguments[0]); // 第一个参数
    }
    example(1, 2, 3);
    
  2. DOM 方法返回的结果

    • 例如,NodeListHTMLCollection 都是类数组对象。
    const divs = document.getElementsByTagName('div');
    console.log(divs); // HTMLCollection
    console.log(divs.length); // 元素数量
    console.log(divs[0]); // 第一个元素
    
  3. 字符串

    • 字符串也可以被视为类数组对象,因为它们有 length 属性,并且可以使用索引访问字符。
    const str = 'hello';
    console.log(str.length); // 5
    console.log(str[0]); // 'h'
    
  4. 其他自定义对象

    • 任何具有 length 属性和索引属性的对象都可以被视为类数组对象。
    const obj = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    console.log(obj.length); // 3
    console.log(obj[0]); // 'a'
    

如何处理类数组对象

由于类数组对象缺乏数组的方法,你通常需要将它们转换为真正的数组,以便使用数组的方法。以下是一些常见的方法:

  1. 使用 Array.prototype.slice.call()

    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const array = Array.prototype.slice.call(arrayLike);
    console.log(array); // ['a', 'b', 'c']
    
  2. 使用 Array.from()

    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const array = Array.from(arrayLike);
    console.log(array); // ['a', 'b', 'c']
    
  3. 使用扩展运算符 ...

    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const array = [...arrayLike];
    console.log(array); // ['a', 'b', 'c']
    
  4. 使用 Array.prototype.concat.apply()

    const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
    const array = Array.prototype.concat.apply([], arrayLike);
    console.log(array); // ['a', 'b', 'c']
    

区别

类数组(Array-like objects)和普通数组(Arrays)在JavaScript中有一些重要的区别。理解这些区别对于正确处理不同类型的对象非常重要。以下是它们之间的主要区别:

1. 定义和结构

  • 普通数组

    • 普通数组是通过 Array 构造函数或数组字面量创建的。
    • 具有固定的结构,包含 length 属性和索引属性,并且支持所有数组的方法(如 push, pop, map, filter 等)。
    • 例如:const arr = [1, 2, 3];
  • 类数组

    • 类数组不是通过 Array 构造函数创建的,但具有 length 属性和索引属性。
    • 缺少数组的所有内置方法,不能直接使用数组的方法。
    • 例如:arguments 对象、DOM 方法返回的 NodeListHTMLCollection

2. 内置方法

  • 普通数组

    • 支持所有数组的内置方法,如 push, pop, shift, unshift, splice, slice, map, filter, reduce, forEach 等。
    • 例如:
      const arr = [1, 2, 3];
      arr.push(4); // [1, 2, 3, 4]
      
  • 类数组

    • 不支持数组的内置方法,因为它们不是真正的数组实例。
    • 例如:
      const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
      // arrayLike.push('d'); // TypeError: arrayLike.push is not a function
      

3. 使用场景

  • 普通数组

    • 用于存储和操作一组有序的数据。
    • 适用于需要频繁增删元素和使用数组方法的场景。
  • 类数组

    • 常见于函数内部的 arguments 对象。
    • DOM 操作中,如 document.querySelectorAll 返回的 NodeListdocument.getElementsByTagName 返回的 HTMLCollection
    • 一些自定义的对象,可能出于性能或其他原因被设计为类数组。

4. 转换为普通数组

  • 类数组转换为普通数组
    • 可以使用多种方法将类数组转换为普通数组,以便使用数组的方法。
    • 例如:
      • 使用 Array.prototype.slice.call()
        const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
        const array = Array.prototype.slice.call(arrayLike);
        console.log(array); // ['a', 'b', 'c']
        
      • 使用 Array.from()
        const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
        const array = Array.from(arrayLike);
        console.log(array); // ['a', 'b', 'c']
        
      • 使用扩展运算符 ...
        const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
        const array = [...arrayLike];
        console.log(array); // ['a', 'b', 'c']
        

5. 性能考虑

  • 普通数组

    • 由于内置了大量方法,使用起来非常方便,但在某些情况下可能会比类数组更消耗资源。
  • 类数组

    • 通常是为了性能优化而设计的,避免了数组方法的额外开销。
    • 例如,NodeListHTMLCollection 是实时更新的,这意味着它们会自动反映 DOM 的变化,而不需要重新获取。
  • 普通数组:是通过 Array 构造函数或数组字面量创建的,支持所有数组的内置方法,适用于需要频繁操作数据的场景。

  • 类数组:具有 length 属性和索引属性,但缺少数组的内置方法,常见于 arguments 对象和 DOM 操作中,可以通过多种方法转换为普通数组。