一切 JavaScript 数组和数组方法!
目录
介绍
许多其他复杂数据结构都基于简单的数组构建。因此,在深入研究其他数据结构之前,对数组有扎实的基础知识和理解至关重要。本文将介绍什么是数组、如何创建数组,以及 20 种方法,涵盖常用方法、一些略微复杂(主要是因为回调函数)的方法,以及一些值得了解的有趣方法。
在阅读本文之前,如果您有兴趣学习更多数据结构,并希望获得更多算法资源,请查看我和Megan Lo合作的系列文章!该系列将重点介绍数据结构和算法,我们的第一篇文章将介绍字符串和数组的交叉。如果您需要复习一下字符串,请点击此处查看她的文章,或者访问我们的合作系列文章,敬请期待更多内容!
事不宜迟,让我们深入数组的奇妙世界吧!
PS:由于这是一篇很长的文章,您可以随意跳过。
什么是数组?
根据 MDN 的定义,JavaScript 数组是“类似列表的对象,其原型包含执行遍历和变异操作的方法”。换句话说,数组按顺序组织其元素,并具有内置方法,允许您根据其位置轻松查找和添加/删除信息。数组位置(也称为索引)从零开始。
有趣的事实:与 Java、C 或 C++ 等非脚本语言不同,JavaScript(作为一种脚本语言)没有静态数组,因为静态数组的大小是固定的,所以你需要提前指定要存储的元素数量。相反,JavaScript 数组是动态的,这意味着它的大小会根据需要增大或缩小。
趣味知识:数组是一种特殊类型的对象!也就是说,数组的对象属性与其元素是分开的,并且您在数组元素上使用的方法不能在其对象属性上使用。要设置或访问数组的对象属性集合,您需要使用括号或点符号。
console.log(typeof new Array()) // “object”
console.log(typeof [1, 2, 3]) // “object”
需要注意的事项:
- 数组具有 O(1) 时间的快速查找,因为您可以简单地根据给定的索引检索元素,而不管数组的大小有多大。
- 在数组中插入或删除元素的时间复杂度为 O(n),因为它需要其他元素“移动”过来腾出空间或填补空缺。
我们如何创建数组?
本质上,创建数组有两种方法:
- 使用数组文字
let literalEmptyArray = []
let literalFilledArray = [1, 2, 3]
- 使用新的构造函数
let constructorEmptyArray = new Array()
let constructorFilledArray = new Array(1, 2, 3)
也就是说,创建数组的第三种方法是使用of
方法!本质上,该of
方法会根据传入的参数数量创建一个新的数组实例,而不管参数的数量或类型。of
方法和Array
构造函数的区别在于它们对参数的处理方式:Array.of(7)
创建一个包含单个元素 7 的数组,而Array(7)
创建一个长度属性为 7 的空数组(注意:这意味着数组中有 7 个空位置,而不是包含实际未定义值的空位置)。
Array.of(1); // [1]
Array.of(1, 2, 3); // [1, 2, 3]
Array.of(undefined); // [undefined]
方法
在深入探讨数组的众多方法(我粗略估计超过 35 种)之前,让我们首先回顾一下破坏性和非破坏性的含义。
- 破坏性:该操作会改变原始数组,这意味着一旦对原始数组执行该操作,您将无法再次获得原始数组的信息。相反,原始数组已被更新。
- 非破坏性:此操作不会改变原始数组,这意味着一旦对原始数组执行此操作,您将获得原始信息。因此,您将能够同时获得原始信息和更新后的信息。
当您最终需要决定使用哪种方法时,理解并了解某种方法何时是破坏性的、何时是非破坏性的至关重要。现在,让我们来看看一些基础的、高级的、有趣的方法!
基本方法
我们将介绍的方法有:pop
、push
、shift
、unshift
、splice
和slice
。为了演示每种方法,基础数组我们将要提到的是:
let iceCream = [“vanilla”, “chocolate”, “strawberry”, “green tea”]
数组方法的 4 个最常见用例是从数组的开头或结尾破坏性地添加或删除元素。
如果您看不到下面的图片,这里是方法的摘要:
push
:将一个项目添加到数组的末尾pop
:从数组末尾删除一个项目unshift
:将一个项目添加到数组的开头shift
:从数组开头删除一个项目
其他常见情况是复制或删除数组的一部分。虽然它们的名称相似,但这些方法本质上是相同的,splice
并且slice
务必记住您希望该操作是破坏性的还是非破坏性的。
splice
:按索引位置删除项目(破坏性)
使用 时splice
,必须传入要从哪个索引(含)开始移除元素。您可以选择包含第二个参数 index 来指定要从哪个索引(含)停止移除元素。如果不包含,该方法将自动移除到数组末尾。此外,从第三个参数开始,所有包含的元素都将被添加到数组中,从 start(第一个参数)开始。如果您未指定任何元素,splice
则只会从数组中删除元素。也就是说,如果没有传入任何参数,则返回值将为空数组。
// general
Array.splice(startIndex)
// including the optional parameters
Array.splice(startIndex, endIndex, newElement)
不要忘记以下示例中的原始数组!
slice
:复制数组(非破坏性)
如果您只是想复制一个数组,则无需传递任何参数。但是,您可以选择包含要复制的起始索引(包含)和结束索引(不包含)。这种方法经常被使用,splice
因为它避免了改变原始数组的“副作用”。
如果您未传递任何参数,则默认情况下将复制整个原始数组。如果任一索引为负数,则从末尾或最后一个元素开始提取(Array.length
-索引)。另一方面,如果您传递的参数大于实际数组(例如,一个包含 5 个元素的数组,但传递的参数指定从 10 开始,到 50 结束),则返回值将为空数组。
// general
Array.slice()
// including the optional parameters
Array.slice(startIndex, endIndex)
不要忘记以下示例中的原始数组!
高级方法
本节将介绍的方法有:reduce
、sort
、concat
、flat
、filter
、、和。继续之前,需要注意的是,许多方法的参数都相同;在本例中join
是、、和。我不会每次都重复,而是把参数的解释留在这里供大家参考!map
find
forEach
filter
map
find
forEach
回调函数可以传入三个参数,其中两个是可选的。必须传入的一个参数是当前值,它表示当前正在处理的元素。另外两个参数是当前元素的索引以及调用该方法的数组。除了回调函数之外,还可以使用thisArg
参数,即执行回调时要使用的值this
。但是,如果回调使用箭头函数,则thisArg
可以省略该参数,因为所有箭头函数都会对this
值进行词法绑定。
reduce
:减少为单个值(破坏性)
本质上,reduce 方法接收一个回调函数,该函数对数组的每个元素执行回调,最终生成单个输出值。你提供的回调(reducer)函数至少必须包含两个参数:累加器 (accumulator) 和当前值 (current value)。累加器会累积回调的返回值;换句话说,它是上次调用回调时返回的累加值;而当前值则是数组中当前正在处理的值。
可选地,reduce 方法可以接受第二个参数,该参数表示初始值。累加器在启动时会将该值作为传入的值。此外,回调函数还可以接受其他参数,例如索引和数组,分别表示当前正在处理的元素的索引以及调用 reduce 方法的数组。
Array.reduce((accumulator, currentValue, index, array) => {
// do something
return accumulator + currentValue
})
sort
:对元素进行排序(破坏性)
当对数组调用此方法时,它会对数组进行就地排序并返回排序后的版本。默认情况下,元素将按升序排序,方法是将元素转换为字符串,然后比较它们的 Unicode 码位。了解它们的排序方式非常重要,因为在数字排序中,9 排在 80 之前,但由于数字被转换为字符串,因此按 Unicode 顺序,“80”排在“9”之前。需要注意的是,所有未定义的元素都会排到数组的末尾。
或者,如果你想更精确地指定排序方式(例如,对于整数),可以传入一个回调(比较)函数,该函数用于比较两个参数;第一个元素和第二个元素,通常分别称为a和b。具体来说,如果回调函数返回:
- 小于 0,表示当前顺序正确;第一个元素将保持在第二个元素之前(a仍将在b之前)
- 0,表示元素彼此相等;顺序将保持彼此相同,但对于所有不同的元素进行排序。
- 大于 0,表示当前顺序不正确;第二个元素将位于第一个元素之前(b在a之前)
Array.sort()
// including the optional parameters
Array.sort((a, b) => a - b)
concat
:合并数组(非破坏性)
此方法用于合并两个或多个数组,返回一个新数组,而不会改变原始数组。更具体地说,新数组包含调用它的数组的所有元素,然后按顺序为每个参数添加参数的元素或参数本身。但是,如果参数是嵌套数组,则不会移除嵌套数组,而只会将其从其所在的数组中移除(一层深度)。
有趣的事实:concat
将原始对象引用复制到新数组中,以便原始数组和新数组都引用同一个对象!因此,如果修改了引用的对象,则新数组和原始数组都可以看到更改。
Array1.concat(Array2)
flat
:创建一个新数组,并将子数组元素连接到其中(非破坏性)
基于该concat
方法,该flat
方法会创建一个新数组,并将所有子数组元素递归地连接到该数组中,直至达到指定的深度。对于单层数组,这与在其累加器上reduce
调用该方法实现相同的效果concat
。另一方面,为了在不使用 flat 方法的情况下实现深层扁平化,可以将递归与 reduce 和 concat 结合使用。
虽然不是必需的,但您可以选择传入一个参数,用于指定嵌套数组结构的展平深度。对于单层数组,此参数默认为 1。
Array.flat()
// including the optional parameters
Array.flat(2)
filter
:返回所有通过提供的测试函数的元素(非破坏性)
此方法会创建一个新数组,其中包含所有通过回调(测试)函数的元素。当它测试调用它的数组的每个元素时,它会返回一个强制转换为 true 的值,如果保留该元素,则返回 false。当值为 false 时,它实际上会跳过该元素,不会将其包含在数组中。如果没有元素通过测试,则返回一个空数组。有关此函数参数的更多信息,请跳回“高级方法”部分的开头!
Array.filter(element => {
// do something
element > 6
})
join
:将数组中的所有元素连接到字符串(非破坏性)
join
通过连接(或称连接)调用该函数的数组的所有元素来创建并返回一个字符串。默认情况下,元素之间用逗号分隔,但您可以指定连接/分隔元素的方式。另一方面,如果数组中只有一个元素,则该元素将以不带分隔符的字符串形式返回;如果没有元素,则返回空字符串。
如上所述,如果您希望使用逗号连接元素,则分隔符参数的参数是可选的。传入空字符串作为参数将导致元素连接时不带任何字符/分隔符。否则,该参数就是您想要在返回的字符串中分隔数组中每对相邻元素的参数。如有必要,分隔符将转换为字符串。
Array.join()
map
:使用回调函数的结果创建一个新数组(非破坏性)
map
接受一个回调函数,该函数会针对其调用的数组的每个元素调用一次。每次执行回调时,它都会将值返回到新数组中,并在最后返回。也就是说,如果您不使用返回的(新)数组和/或不从回调返回值,则使用该map
方法将被视为反模式。您应该使用该forEach
方法或 for 循环。有关此函数参数的更多信息,请跳回“高级方法”部分的开头!
Array.filter(element => {
// do something
element * 2
})
find
:返回第一个满足所提供函数的元素的值(非破坏性)
该find
方法仅返回满足回调(测试)函数的元素的第一个值。如果没有元素通过测试,该find
方法将返回 undefined。也就是说,如果您想返回元素的索引而不是其值,可以使用该findIndex
方法。有关此函数参数的更多信息,请跳回“高级方法”部分的开头!
Array.find(element => {
// do something
element > 6
})
forEach
:循环遍历数组(非破坏性)
类似于循环for
,forEach
对数组中的每个元素执行一次回调函数。虽然该forEach
方法不会改变调用它的数组,但回调函数可能会改变它。不过,该forEach
方法需要一个同步函数,始终返回 undefined,并且不可链式调用。因此,典型的用例是在链式调用的末尾执行副作用。有关此函数参数的更多信息,请跳回“高级方法”部分的开头!
Array.forEach(element => console.log(element))
有趣的方法
现在,是时候介绍一些“有趣”的方法了!我们将在这里介绍的方法有:toString
、includes
、fill
、indexOf
和findIndex
。
toString
:返回表示数组及其元素的字符串(非破坏性)
正如其名称所示,该toString
方法将调用它的数组元素转换为字符串。更具体地说,此方法将数组连接起来并返回一个字符串,该字符串包含用逗号分隔的各个数组元素。
有趣的事实toString
:当数组表示为文本值或在字符串连接中引用数组时,JavaScript 会自动调用该方法。
Array.toString()
includes
:如果数组中存在某个值,则返回布尔值(非破坏性)
includes
判断数组元素中是否包含某个值,并根据情况返回 true 或 false。它通过检查每个元素是否与该值相等来实现,而不是使用测试回调函数。也就是说,如果您需要判断任何元素是否满足提供的测试回调函数,可以使用 some 方法。
您必须传入的参数是您希望该方法搜索的值;请注意,比较字符串和字符时,includes
它区分大小写。可选的第二个参数是开始搜索值的索引,默认值为零。也就是说,如果传入的索引大于或等于数组的长度,则返回 false,并且不会搜索该数组。另一方面,如果索引为负数,则该方法使用它的绝对值作为从数组末尾开始搜索的元素数。
Array.includes(searchValue)
fill
:用静态值填充数组的所有元素(破坏性)
该fill
方法将数组中从起始索引到结束索引的所有元素更改为静态值。然后返回已填充值的修改后的数组。
有三个参数,但只有第一个是必需的。必须传入的第一个参数是用于填充数组的值。需要注意的是,数组中的所有元素都将是该精确值。另外两个可选参数分别是起始索引(默认值为零)和结束索引(默认值为数组长度)。
Array.fill(staticValue)
indexOf & findIndex:查找数组中某个项目的索引(非破坏性)
两者相似之处在于,它们都返回第一个满足条件的索引。然而,while 方法findIndex
基于满足测试回调函数的元素,indexOf
检查每个元素是否与值相等。此外,如果要搜索的元素不存在,则返回 -1 ;如果没有任何元素满足回调函数indexOf
,则返回 -1 findIndex
。也就是说,如果您需要查找任何元素是否满足提供的测试函数,可以使用 some 方法。
findIndex
具有与高级方法部分开头详述的相同参数。另一方面,indexOf
它接受一个参数,即要搜索的元素,以及可选的开始搜索的索引。如果包含第二个参数,即开始搜索的索引,并且索引大于或等于数组的长度,则返回 -1,这意味着不会搜索该数组。
笔记:
Array.indexOf(searchValue)
Array.indexOf(searchValue, startIndex)
Array.findIndex(element => {
// do something
element === "cat"
})
结论
恭喜!我宣布你成为“JavaScript 数组和(大多数)数组方法大师”!
不过说真的,这些信息量很大,希望以后你能参考一下!以下是一些关键要点,以及我们涵盖的方法的回顾:
- 数组按顺序组织其项目,并具有内置方法,允许您根据其位置轻松查找和添加/删除信息。
- JavaScript 数组是一种特殊类型的对象,与非脚本语言不同,它是动态的
- 要创建数组,您可以使用数组文字、new 构造函数或 of 方法
- 此外,您还可以使用扩展运算符复制、连接数组以及将字符串转换为数组
- 添加、删除或复制数组的基本方法:
pop
,,,,,,push
shift
unshift
splice
slice
- 高级合并方法:
reduce
,,,concat
flat
join
- 高级方法根据回调执行某些操作:
sort
,,,,filter
map
find
forEach
- 与值或索引有关的有趣方法:
includes
,,indexOf
findIndex
toString
有趣的方法:fill
如果您读到了最后,非常感谢您的阅读,希望本文对您有所帮助!建议您看看我朋友 Megan 的《何时在 JavaScript 中使用这些字符串方法》,其中有一篇类似的关于字符串方法的文章。也别忘了我和 Megan合作的文章,其中介绍了字符串和数组的交叉用法!