Array

与其他编程语言中的数组一样, Array 对象支持 在单个变量名下存储多个元素 ,并具有 执行常见数组操作 的成员。

描述

在 JavaScript 中,数组不是 基本类型 ,而是具有以下核心特征的 Array 对象:

  • JavaScript 数组是可调整大小的,并且可以包含不同的 数据类型 。(当不需要这些特征时,可以使用 类型化数组 。)
  • JavaScript 数组不是关联数组 ,因此,不能使用任意字符串作为索引访问数组元素,但必须使用非负整数(或它们各自的字符串形式)作为索引访问。
  • JavaScript 数组的 索引从 0 开始 :数组的第一个元素在索引 0 处,第二个在索引 1 处,以此类推,最后一个元素是数组的 length 属性减去 1 的值。
  • JavaScript 数组复制操作 创建 浅拷贝 。( 所有 JavaScript 对象的标准内置复制操作都会创建浅拷贝,而不是 深拷贝 )。
  • 数组下标

    Array 对象不能使用任意字符串作为元素索引(如 关联数组 ),必须使用非负整数(或它们的字符串形式)。通过非整数设置或访问不会设置或从数组列表本身检索元素,但会设置或访问与该数组的 对象属性集合 相关的变量。数组的对象属性和数组元素列表是分开的,数组的 遍历和修改操作 不能应用于这些命名属性。

    数组元素是对象属性,就像 toString 是属性一样(具体来说, toString() 是一种方法)。然而,尝试按以下方式访问数组的元素会抛出语法错误,因为属性名无效:

    console.log(arr.0); // 语法错误
    

    JavaScript 语法要求使用方括号表示法而不是点号表示法来访问以数字开头的属性。也可以用引号包裹数组下标(例如,years['2'] 而不是 years[2]),尽管通常没有必要。

    JavaScript 引擎通过隐式的 toString,将 years[2] 中的 2 强制转换为字符串。因此,'2''02' 将指向 years 对象上的两个不同的槽位,下面的例子可能是 true

    console.log(years['2'] !== years['02']);
    

    只有 years['2'] 是一个实际的数组索引。years['02'] 是一个在数组迭代中不会被访问的任意字符串属性。

    长度与数值属性的关系

    JavaScript 数组的 length 属性和数值属性是连接的。

    一些内置数组方法(例如 join() slice() indexOf() 等)在被调用时会考虑到数组的 length 属性的值。

    其他方法(例如, push() splice() 等)也会更新数组的 length 属性。

    const fruits = [];
    fruits.push('banana', 'apple', 'peach');
    console.log(fruits.length); // 3
    

    当在 JavaScript 数组上设置一个属性时,如果该属性是一个有效的数组索引并且该索引在数组的当前边界之外,引擎将相应地更新数组的 length 属性:

    fruits[5] = "mango";
    console.log(fruits[5]); // 'mango'
    console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
    console.log(fruits.length); // 6
    

    增加 length

    fruits.length = 10;
    console.log(fruits); // ['banana', 'apple', 'peach', empty x 2, 'mango', empty x 4]
    console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
    console.log(fruits.length); // 10
    console.log(fruits[8]); // undefined
    

    但是,减少 length 属性会删除元素。

    fruits.length = 2;
    console.log(Object.keys(fruits)); // ['0', '1']
    console.log(fruits.length); // 2
    

    这将在 Array/length 页中进一步解释。

    数组方法和空槽

    稀疏数组 中的空槽在数组方法之间的行为不一致。通常,旧方法会跳过空槽,而新方法将它们视为 undefined

    在遍历多个元素的方法中,下面的方法在访问索引之前执行 in 检查,并且不将空槽与 undefined 合并:

  • concat()
  • copyWithin()
  • every()
  • filter()
  • flat()
  • flatMap()
  • forEach()
  • indexOf()
  • lastIndexOf()
  • map()
  • reduce()
  • reduceRight()
  • reverse()
  • slice()
  • some()
  • sort()
  • splice()
  • 关于它们是如何处理空槽的,请参阅每个方法的页面。

    这些方法将空槽视为 undefined

  • entries()
  • fill()
  • find()
  • findIndex()
  • findLast()
  • findLastIndex()
  • group()
  • groupToMap()
  • includes()
  • join()
  • keys()
  • toLocaleString()
  • values()
  • 复制方法和修改方法

    有些方法不会修改调用该方法的现有数组,而是返回一个新的数组。它们通过首先构造一个新数组,然后填充元素来实现。复制始终是 浅层次的 ——该方法从不复制一开始创建的数组之外的任何内容。原始数组的元素将按以下方式复制到新数组中:

  • 对象:对象引用被复制到新数组中。原数组和新数组都引用同一个对象。也就是说,如果一个被引用的对象被修改,新数组和原数组都可以看到更改。
  • 基本类型,如字符串、数字和布尔值(不是 String Number Boolean 对象):它们的值被复制到新数组中。
  • 其他方法会改变调用该方法的数组,在这种情况下,它们的返回值根据方法的不同而不同:有时是对相同数组的引用,有时是新数组的长度。

    以下方法通过访问 this.constructor[Symbol.species] 来创建新数组,以确定要使用的构造函数:

  • concat()
  • filter()
  • flat()
  • flatMap()
  • map()
  • slice()
  • splice() (构造返回的已删除元素数组)
  • 以下方法总是使用 Array 基础构造函数创建新数组:

  • toReversed()
  • toSorted()
  • toSpliced() (en-US)
  • with()
  • 注意, group() groupToMap() 不使用 @@species 为每个组条目创建新数组,而是始终使用普通的 Array 构造函数。从概念上讲,它们也不是复制方法。

    下表列出了会修改原始数组的方法,以及相应的非修改方法:

    相应的非修改方法

    将改变原数组的方法转换为非修改方法的一种简单方式是使用 展开语法 slice() 先创建一个副本:

    arr.copyWithin(0, 1, 2); // 改变了 arr
    const arr2 = arr.slice().copyWithin(0, 1, 2); // 不改变 arr
    const arr3 = [...arr].copyWithin(0, 1, 2); // 不改变 arr
    

    迭代方法

    许多数组方法接受一个回调函数作为参数。回调函数按顺序为数组中的每个元素调用,且最多调用一次,并且回调函数的返回值用于确定方法的返回值。它们都具有相同的方法签名:

    method(callbackFn, thisArg)
    

    其中 callbackFn 接受三个参数

    element

    数组中当前正在处理的元素。

    index

    正在处理的元素在数组中的索引。

    array

    调用该方法的数组。

    callbackFn 的返回值取决于调用的数组方法。

    thisArg 参数(默认为 undefined)将在调用 callbackFn 时用作 this 值。最终由 callbackFn 观察到的 this 值根据通常的规则 确定:如果 callbackFn非严格模式(译注:正常模式/马虎模式),原始 this 值将被包装为对象,并将 undefined/null 替换为 globalThis。对于使用 箭头函数 定义的任何 callbackFn 来说,thisArg 参数都是无关紧要的,因为箭头函数没有自己的 this 绑定。

    所有迭代方法都是复制方法通用方法,尽管它们在处理空槽时的行为不同。

    以下方法是迭代方法:

  • every()
  • filter()
  • find()
  • findIndex()
  • findLast()
  • findLastIndex()
  • flatMap()
  • forEach()
  • group()
  • groupToMap()
  • map()
  • some()
  • 特别地,every()find()findIndex()findLast()findLastIndex()some() 并不总是在每个元素上调用 callbackFn——它们在确定返回值后立即停止迭代。

    还有两个方法接受一个回调函数,并对数组中的每个元素最多运行一次,但它们的方法签名与典型的迭代方法略有不同(例如,它们不接受 thisArg):

  • reduce()
  • reduceRight()
  • sort() 方法也接受一个回调函数,但它不是一个迭代方法。它会就地修改数组,不接受 thisArg,并且可能在索引上多次调用回调函数。

    通用数组方法

    数组方法总是通用的——它们不访问数组对象的任何内部数据。它们只通过 length 属性和索引访问数组元素。这意味着它们也可以在类数组对象上调用。

    const arrayLike = {
      0: "a",
      1: "b",
      length: 2,
    console.log(Array.prototype.join.call(arrayLike, "+")); // 'a+b'
    

    长度属性的规范化

    length 属性被转换为一个数字,被截断为一个整数,然后固定为 0 到 253 - 1 之间的范围。NaN 变成 0,所以即使 length 没有出现或 undefined,它也会表现得好像它的值是 0

    JavaScript 避免将 length 设置为不安全的整数。如果 length 将被设置为大于 253 - 1 的数字,则所有内置方法都将抛出 TypeError。但是,由于数组的 length 属性在设置为大于 232 时会抛出错误,因此通常不会达到安全整数阈值,除非该方法在非数组对象上调用。

    Array.prototype.flat.call({}); // []
    

    一些数组方法会设置数组对象的 length 属性。它们总是在规范化后设置值,因此 length 总是以整数结尾。

    const a = { length: 0.7 };
    Array.prototype.push.call(a);
    console.log(a.length); // 0
    

    类数组对象

    术语类数组对象指的是在上面描述的 length 转换过程中不抛出的任何对象。在实践中,这样的对象应该实际具有 length 属性,并且索引元素的范围在 0length - 1 之间。(如果它没有所有的索引,它将在功能上等同于稀疏数组。)

    许多 DOM 对象都是类数组对象——例如 NodeListHTMLCollectionarguments 对象也是类数组对象。你可以在它们上调用数组方法,即使它们本身没有这些方法。

    function f() {
      console.log(Array.prototype.join.call(arguments, "+"));
    f("a", "b"); // 'a+b'
      
    Array()

    创建一个新的 Array 对象。

    get Array[@@species]

    返回 Array 构造函数。

  • Array.from()
  • : 从数组类对象或可迭代对象创建一个新的 Array 实例。
  • Array.fromAsync()
  • 从异步可迭代、可迭代或类数组对象创建新的 Array 实例。
  • Array.isArray()
  • : 如果参数是数组则返回 true ,否则返回 false
  • Array.of()
  • : 创建一个新的 Array 实例,具有可变数量的参数,而不管参数的数量或类型。
  • 实例属性

    以下属性在 Array.prototype 上定义,并由所有 Array 实例共享。

    Array.prototype.constructor

    创建实例对象的构造函数。对于 Array 实例,初始值是 Array 构造函数。

    Array.prototype[@@unscopables]

    包含 ES2015 版本之前 ECMAScript 标准中没有包含的属性名,在使用 with 绑定语句时会被忽略。

    以下属性是每个 Array 实例自有的属性。

    Array.prototype.length

    反映数组中元素的数量。

    Array.prototype.at()

    返回给定索引处的数组元素。接受从最后一项往回计算的负整数。

    Array.prototype.concat()

    返回一个新数组,该数组由被调用的数组与其他数组或值连接形成。

    Array.prototype.copyWithin()

    在数组内复制数组元素序列。

    Array.prototype.entries()

    返回一个新的 数组迭代器 对象,其中包含数组中每个索引的键/值对。

    Array.prototype.every()

    如果调用数组中的每个元素都满足测试函数,则返回 true

    Array.prototype.fill()

    用静态值填充数组中从开始索引到结束索引的所有元素。

    Array.prototype.filter()

    返回一个新数组,其中包含调用所提供的筛选函数返回为 true 的所有数组元素。

    Array.prototype.find()

    返回数组中满足提供的测试函数的第一个元素的值,如果没有找到合适的元素,则返回 undefined

    Array.prototype.findIndex()

    返回数组中满足提供的测试函数的第一个元素的索引,如果没有找到合适的元素,则返回 -1

    Array.prototype.findLast()

    返回数组中满足提供的测试函数的最后一个元素的值,如果没有找到合适的元素,则返回 undefined

    Array.prototype.findLastIndex()

    返回数组中满足所提供测试函数的最后一个元素的索引,如果没有找到合适的元素,则返回 -1

    Array.prototype.flat()

    返回一个新数组,所有子数组元素递归地连接到其中,直到指定的深度。

    Array.prototype.flatMap()

    对调用数组的每个元素调用给定的回调函数,然后将结果平展一层,返回一个新数组。

    Array.prototype.forEach()

    对调用数组中的每个元素调用函数。

    Array.prototype.group()

    根据测试函数返回的字符串,将数组的元素分组到一个对象中。

    Array.prototype.groupToMap()

    根据测试函数返回的值,将数组的元素分组到 Map 中。

    Array.prototype.includes()

    确定调用数组是否包含一个值,根据情况返回 true false

    Array.prototype.indexOf()

    返回在调用数组中可以找到给定元素的第一个(最小)索引。

    Array.prototype.join()

    将数组的所有元素连接为字符串。

    Array.prototype.keys()

    返回一个新的 数组迭代器 ,其中包含调用数组中每个索引的键。

    Array.prototype.lastIndexOf()

    返回在调用数组中可以找到给定元素的最后一个(最大)索引,如果找不到则返回 -1

    Array.prototype.map()

    返回一个新数组,其中包含对调用数组中的每个元素调用函数的结果。

    Array.prototype.pop()

    从数组中移除最后一个元素并返回该元素。

    Array.prototype.push()

    在数组末尾添加一个或多个元素,并返回数组新的 length

    Array.prototype.reduce()

    对数组的每个元素(从左到右)执行用户提供的 “reducer” 回调函数,将其简化为单个值。

    Array.prototype.reduceRight()

    对数组的每个元素(从右到左)执行用户提供的 “reducer” 回调函数,将其简化为单个值。

    Array.prototype.reverse()

    反转数组中元素的顺序。(前面变成后面,后面变成前面。)

    Array.prototype.shift()

    从数组中移除第一个元素并返回该元素。

    Array.prototype.slice()

    提取调用数组的一部分并返回一个新数组。

    Array.prototype.some()

    如果调用数组中至少有一个元素满足提供的测试函数,则返回 true

    Array.prototype.sort()

    对数组的元素进行排序并返回该数组。

    Array.prototype.splice()

    从数组中添加和/或删除元素。

    Array.prototype.toLocaleString()

    返回一个表示调用数组及其元素的本地化字符串。重写 Object.prototype.toLocaleString() 方法。

    Array.prototype.toReversed()

    返回一个新数组,该数组的元素顺序被反转,但不改变原始数组。

    Array.prototype.toSorted()

    返回一个新数组,其中元素按升序排序,而不改变原始数组。

    Array.prototype.toSpliced() (en-US)

    返回一个新数组,在给定索引处删除和/或替换了一些元素,而不改变原始数组。

    Array.prototype.toString()

    返回一个表示调用数组及其元素的字符串。重写 Object.prototype.toString() 方法。

    Array.prototype.unshift()

    在数组的前面添加一个或多个元素,并返回数组新的 length

    Array.prototype.values()

    返回一个新的 数组迭代器 对象,该对象包含数组中每个索引的值。

    Array.prototype.with()

    返回一个新数组,其中给定索引处的元素替换为给定值,而不改变原始数组。

    Array.prototype[@@iterator]()

    默认情况下,该方法为 values() 方法的别名。

    示例

    本节提供一些 JavaScript 中常见的数组操作示例。

    备注: 如果你还不熟悉数组的基础知识,可以考虑先读一下 JavaScript 第一步:数组 ,它解释了 数组是什么 ,还包括其他常见的数组操作示例。