28.数组(Array
)
28.1。 JavaScript 中的数组的两个角色
数组在 JavaScript 中扮演两个角色:
- 元组:Arrays-as-tuples 具有固定数量的索引元素。这些元素中的每一个都可以具有不同的类型。
- 序列:Arrays-as-sequences 具有可变数量的索引元素。每个元素都具有相同的类型。
实际上,这两种角色也有其混合物。
值得注意的是,Arrays-as-sequences 非常灵活,您可以将它们用作(传统)数组,堆栈和队列(请参阅本章末尾的练习)。
28.2。基本数组操作
28.2.1。数组:创建,阅读,写作
创建数组的最佳方法是通过 数组字面值 :
Array 字面值以方括号[]
开头和结尾。它创建一个包含三个 元素 的数组:'a'
,'b'
和'c'
。
要读取 Array 元素,请将索引放在方括号中(索引从零开始):
要更改 Array 元素,请为具有索引的 Array 指定:
数组索引的范围是 32 位(不包括最大长度):[0,2 32 -1)
28.2.2。数组:.length
每个 Array 都有一个属性.length
,可用于读取和更改(!)数组中元素的数量。
数组的长度始终是最高的索引加一:
如果在长度的索引处写入数组,则追加一个元素:
(破坏性地)附加元素的另一种方法是通过 Array 方法.push()
:
如果设置.length
,则通过删除元素来修剪数组:
练习:通过.push()
删除空行
exercises/arrays/remove_empty_lines_push_test.js
28.2.3。传播到数组
在 Arrry 字面值中, 扩展元素 由三个点(...
)组成,后跟一个表达式。它导致表达式被评估然后迭代。每个迭代值都成为一个额外的 Array 元素。例如:
传播很方便将数组和其他迭代连接到数组中:
28.2.4。数组:列出索引和条目
方法.keys()
列出数组的索引:
.keys()
返回一个可迭代的。在 A 行,我们传播以获得一个数组。
列表数组索引与列表属性不同。当你执行后者时,你得到索引 - 但作为字符串 - 加上非索引属性键:
方法.entries()
将数组的内容列为[index,element]对:
28.2.5。数值是一个数组吗?
这两种方法可以检查值是否为数组:
instanceof
通常很好。如果某个值可能来自另一个 域 ,则需要Array.isArray()
。粗略地说,领域是 JavaScript 全局范围的一个实例。一些领域彼此隔离(例如浏览器中的 Web Workers ),但也有一些领域可以移动数据(例如浏览器中的同源 iframe)。 x instanceof Array
检查x
的原型链,因此如果x
是来自另一个域的数组,则返回false
。
typeof
将数组归类为对象:
28.3。 for-of
和数组
我们已经遇到了for-of
循环。本节简要介绍如何将它用于数组。
28.3.1。 for-of
:迭代元素
以下for-of
循环遍历数组的元素。
28.3.2。 for-of
:迭代[index,element]对
以下for-of
循环遍历[index,element]对。解构(稍后描述)为我们提供了在for-of
的头部设置index
和element
的便捷语法。
28.4。类似于数组的对象
一些使用 Arrays 的操作只需要最小值:值必须只是 类似于 。类似数组的值是具有以下属性的对象:
.length
:保存类似 Array 的对象的长度。['0']
:将元素保持在索引 0 处。(等等)
ArrayLike
的 TypeScript 接口如下所示。
Array.from()
接受类似 Array 的对象并将它们转换为 Arrays:
类似于数组的对象曾经在 ES6 之前很常见;现在你不经常看到它们。
28.5。将可迭代和类似数组的值转换为数组
将可迭代和类似数组的值转换为数组有两种常用方法:扩展和Array.from()
。
28.5.1。通过传播将迭代转换为数组(...
)
在 Array 文本中,通过...
传播将任何可迭代对象转换为一系列 Array 元素。例如:
转换有效,因为 DOM 集合是可迭代的。
28.5.2。通过Array.from()
将可迭代和类似数组的对象转换为数组(高级)
Array.from()
可以使用两种模式。
28.5.2.1。 Array.from()
的模式 1:转换
第一种模式具有以下类型签名:
接口Iterable
在中显示了介绍迭代的章节。本章前面出现[HTD2]接口ArrayLike
。
使用单个参数,Array.from()
将任何可迭代或类似 Array 的数据转换为数组:
28.5.2.2。 Array.from()
的模式 2:转换和映射
Array.from()
的第二种模式涉及两个参数:
在这种模式下,Array.from()
做了几件事:
- 它迭代
iterable
。 - 它将
mapFunc
应用于每个迭代值。 - 它将结果收集到一个新数组中并返回它。
可选参数thisArg
指定mapFunc
的this
。
这意味着我们将从具有T
类型的元素的 iterable 转变为具有U
类型的元素的 Array。
这是一个例子:
28.6。创建和填充任意长度的数组
创建数组的最佳方法是通过数组字面值。但是,您不能总是使用一个:数组可能太大,您可能在开发过程中不知道它的长度,或者您可能希望保持其长度灵活。然后我推荐以下用于创建和填充数组的技术:
你需要创建一个你将完全填充的空数组,稍后呢?
请注意,结果有 3 个孔 - 数组字面值中的最后一个逗号始终被忽略。
你需要创建一个用原始值初始化的数组吗?
警告:如果对对象使用
.fill()
,则每个 Array 元素将引用同一个对象。你需要创建一个用对象初始化的数组吗?
你需要创建一系列整数吗?
如果您正在处理整数或浮点数的数组,请考虑 类型数组 - 这是为此目的而创建的。
28.7。多维数组
JavaScript 没有真正的多维数组;你需要求助于其元素为数组的数组:
观察:
我们通过为索引为当前长度的插槽分配值来增长数组。
每个维度 - 除了最后一个维度 - 是一个数组,其元素是下一个维度(行 A,行 B)。
最后一个维度包含实际值(第 C 行)。
28.8。更多数组功能(高级)
在本节中,我们将介绍在使用 Arrays 时经常遇到的现象。
28.8.1。数组元素是(稍微特殊)属性
您认为 Array 元素是特殊的,因为您通过数字访问它们。但是这样做的方括号运算符([ ]
)与用于访问属性的运算符相同。并且,根据语言规范,它将任何值(不是符号)强制转换为字符串。因此:数组元素是(几乎)正常属性(A 行),如果使用数字或字符串作为索引(行 B 和 C),则无关紧要:
更令人困惑的是,这只是语言规范如何定义事物(JavaScript 的理论,如果你愿意的话)。大多数 JavaScript 引擎都经过优化,并且使用数字(甚至是整数)来访问 Array 元素(如果你愿意,可以使用 JavaScript 的实践)。
用于 Array 元素的属性键(字符串!)称为 索引 。字符串str
是将其转换为 32 位无符号整数并返回后生成原始值的索引。写成公式:
ToString(ToUint32(key)) === key
JavaScript 在列出所有对象的属性键时特别对待索引!它们总是排在第一位并按数字排序,而不是按字典顺序排列('10'
将在'2'
之前出现):
请注意,.length
,.entries()
和.keys()
将数组索引视为数字并忽略非索引属性:
我们使用扩展元素(...
)将.keys()
和.entries()
返回的迭代转换为数组。
28.8.2。数组是字典,可以有洞
JavaScript 支持两种数组:
- 密集数组:是数组形成连续序列的数组。这是迄今为止我们见过的唯一一种数组。
- 稀疏数组:包含空洞的数组。也就是说,一些指数缺失。
一般来说,最好避免漏洞,因为它们会使代码更复杂,并且不会被 Array 方法一致地处理。此外,JavaScript 引擎优化密集数组,因此它们更快。
您可以在分配元素时通过跳过索引来创建孔:
在 A 行中,我们使用Object.keys()
,因为arr.keys()
将孔视为undefined
元素并且不会显示它们。
另一种创建漏洞的方法是跳过数组字面值中的元素:
你可以删除数组元素:
有关 JavaScript 如何处理数组中的漏洞的更多信息,请参阅“探索 ES6”。
28.9。添加和删除元素(破坏性和非破坏性)
JavaScript 的Array
非常灵活,更像是数组,堆栈和队列的组合。本节探讨添加和删除 Array 元素的方法。大多数操作可以破坏性地(修改数组)和非破坏性地(生成修改的副本)执行。
28.9.1。预先添加元素和数组
在下面的代码中,我们破坏性地将单个元素添加到arr1
,将数组添加到arr2
:
传播让我们将数组移入arr2
。
非破坏性预先支付通过扩散元素完成:
28.9.2。附加元素和数组
在下面的代码中,我们破坏性地将单个元素附加到arr1
,将数组附加到arr2
:
传播让我们将数组推入arr2
。
非破坏性附加是通过扩散元素完成的:
28.9.3。删除元素
这是删除 Array 元素的三种破坏性方法:
快速参考部分中详细介绍了.splice()
。
通过 rest 元素进行解构使您可以从数组的开头非破坏性地删除元素(稍后将介绍解构)。
唉,一个 rest 元素必须始终位于 Array 中。因此,您只能使用它来提取后缀。
练习:通过数组实现队列
exercises/arrays/queue_via_array_test.js
28.10。方法:迭代和转换(.find()
,.map()
,.filter()
等)
在本节中,我们将介绍用于迭代数组和转换数组的 Array 方法。在我们这样做之前,让我们考虑两种不同的迭代方法。它将帮助我们理解这些方法的工作原理。
28.10.1。外部迭代与内部迭代
假设您的代码想要迭代对象“内部”的值。这样做的两种常用方法是:
外部迭代(pull):您的代码通过迭代协议向对象请求值。例如,
for-of
循环基于 JavaScript 的迭代协议:有关更长的示例,请参阅有关同步生成器的章节。
内部迭代(推送):您将回调函数传递给对象的方法,并且该方法将值提供给回调。例如,Arrays 有方法
.forEach()
:有关更长的示例,请参阅有关同步生成器的章节。
我们接下来要看的方法都使用内部迭代。
28.10.2。迭代和转换方法的回调
迭代或转换方法的回调,具有以下签名:
也就是说,回调有三个参数(可以忽略其中任何一个):
value
是最重要的一个。此参数保存当前正在处理的迭代值。index
还可以告诉回调迭代值的索引是什么。array
指向当前 Array(方法调用的接收者)。一些算法需要引用整个数组 - 例如搜索它的答案。此参数允许您为此类算法编写可重用的回调。
预期返回的回调取决于传递给它的方法。可能性包括:
- 没什么(
.forEach()
)。 - 布尔值(
.find()
)。 - 任意值(
.map()
)。
28.10.3。搜索元素:.find()
,.findIndex()
.find()
返回其回调返回 truthy 值的第一个元素:
.findIndex()
返回其回调返回 truthy 值的第一个元素的索引:
.findIndex()
可以实现如下:
28.10.4。 .map()
:复制时给予元素新值
.map()
返回接收器的副本。副本的元素是将map
的回调参数应用于接收器元素的结果。
所有这些都通过示例更容易理解:
注意:_
只是另一个变量名。
.map()
可以实现如下:
练习:通过.map()
编号行
exercises/arrays/number_lines_test.js
28.10.5。 .flatMap()
:映射到零个或多个值
Array<T>.prototype.flatMap()
的类型签名是:
.map()
和.flatMap()
都将函数f
作为控制输入数组如何转换为输出数组的参数:
- 使用
.map()
,每个输入数组元素都被转换为一个输出元素。也就是说,f
返回单个值。 - 使用
.flatMap()
,每个输入数组元素都转换为零个或多个输出元素。也就是说,f
返回一个值数组(它也可以返回非数组值,但这很少见)。
这是.flatMap()
的作用:
28.10.5.1。一个简单的实现
您可以按如下方式实现.flatMap()
。注意:此实现比内置版本更简单,例如,执行更多检查。
什么是.flatMap()
有用?我们来看看用例吧!
28.10.5.2。使用案例:同时过滤和映射
Array 方法.map()
的结果始终与调用它的 Array 的长度相同。也就是说,它的回调不能跳过它不感兴趣的数组元素。
.flatMap()
执行此操作的功能在下一个示例中很有用:processArray()
返回一个数组,其中每个元素都是包装值或包装错误。
以下代码显示processArray()
正在运行:
.flatMap()
使我们能够仅从results
中提取值或仅提取错误:
28.10.5.3。用例:映射到多个值
Array 方法.map()
将每个输入 Array 元素映射到一个输出元素。但是如果我们想将它映射到多个输出元素呢?
这在以下示例中变得必要:
- 输入:
['a', 'b', 'c']
- 输出:
['<span>a</span>', ', ', '<span>b</span>', ', ', '<span>c</span>']
进行此转换的函数wrap()
类似于您为前端库 React 编写的代码:
练习:.flatMap()
exercises/arrays/convert_to_numbers_test.js
exercises/arrays/replace_objects_test.js
28.10.6。 .filter()
:只保留一些元素
Array 方法.filter()
返回一个 Array,收集回调返回 truthy 值的所有元素。
例如:
.filter()
可以实现如下:
练习:通过.filter()
删除空行
exercises/arrays/remove_empty_lines_filter_test.js
28.10.7。 .reduce()
:从数组中获取值(高级)
方法.reduce()
是一个用于计算数组“摘要”的强大工具。摘要可以是任何类型的值:
- 一个号码。例如,所有 Array 元素的总和。
- 数组。例如,Array 的副本,元素乘以 2。
- 等等。
此操作在函数式编程中也称为foldl
(“向左折叠”)并且在那里流行。需要注意的是,它可能使代码难以理解。
.reduce()
具有以下类型签名(在Array<T>
内):
T
是数组元素的类型,U
是摘要的类型。两者可能有所不同,也可能没有。 accumulator
只是“摘要”的另一个名称。
要计算数组arr
的摘要,.reduce()
将所有数组元素一次一个地提供给其回调:
callback
将先前计算的摘要(存储在其参数accumulator
中)与当前的 Array 元素组合,并返回下一个accumulator
。 .reduce()
的结果是最终累加器 - callback
的最后一个结果,在它访问了所有元素之后。
换句话说:callback
完成大部分工作,.reduce()
只是以有用的方式调用它。
你可以说回调将数组元素折叠到累加器中。这就是为什么这个操作在函数式编程中称为“折叠”。
28.10.7.1。第一个例子
让我们看一下.reduce()
的实例:函数addAll()
计算数组arr
中所有数字的总和。
在这种情况下,累加器保存callback
已经访问过的所有数组元素的总和。
结果6
是如何从 A 行的数组中得出的?通过以下callback
调用:
callback(0, 1) --> 1
callback(1, 2) --> 3
callback(3, 3) --> 6
笔记:
- 第一个参数是电流累加器(从
.reduce()
的参数init
开始)。 - 第二个参数是当前的 Array 元素。
- 结果是下一个累加器。
callback
的最后结果也是.reduce()
的结果。
或者,我们可以通过for-of
循环实现addAll()
:
很难说这两个实现中的哪一个“更好”:基于.reduce()
的实现更简洁,而基于for-of
的实现可能更容易理解 - 特别是如果你不熟悉函数式编程。
28.10.7.2。示例:通过.reduce()
查找索引
以下函数是 Array 方法.indexOf()
的实现。它返回给定searchValue
出现在 Array arr
内的第一个索引:
.reduce()
的一个限制是您无法提前完成(在for-of
循环中,您可以break
)。在这里,一旦我们找到了我们想要的东西,我们就不会做任何事情。
28.10.7.3。示例:加倍数组元素
函数double(arr)
返回inArr
的副本,其元素全部乘以 2:
我们通过推入修改初始值[]
。 double()
的非破坏性,功能更强的版本如下:
这个版本更优雅,但也更慢并且使用更多内存。
练习:.reduce()
map()
通过.reduce()
:exercises/arrays/map_via_reduce_test.js
filter()
通过.reduce()
:exercises/arrays/filter_via_reduce_test.js
countMatches()
通过.reduce()
:exercises/arrays/count_matches_via_reduce_test.js
28.11。 .sort()
:排序数组
.sort()
具有以下类型定义:
.sort()
始终对元素的字符串表示进行排序。这些表示通过<
进行比较。该运算符按字典顺序比较 (第一个字符最重要)。您可以在比较数字时看到:
比较人类语言字符串时,您需要知道它们是根据它们的代码单元值(字符代码)进行比较的:
正如您所看到的,所有非重音大写字母都出现在所有重音字母之前,这些字母位于所有重音字母之前。如果要对人类语言进行适当的排序,请使用Intl
, JavaScript 国际化 API 。
最后,.sort()
将 排序到位 :它改变并返回其接收器:
28.11.1。自定义排序顺序
您可以通过参数compareFunc
自定义排序顺序,该参数返回一个数字:
- 否定如果
a < b
- 如果
a === b
为零 - 如果
a > b
为正
记住这些规则的提示:负数是 小于 零(等)。
28.11.2。排序数字
您可以使用以下辅助函数来比较数字:
以下是一个快速而肮脏的选择。它的缺点是它很神秘,存在数字溢出的风险:
28.11.3。排序对象
如果要对对象进行排序,还需要使用比较函数。例如,以下代码显示了如何按年龄对对象进行排序。
练习:按名称排序对象
exercises/arrays/sort_objects_test.js
28.12。快速参考:Array<T>
传说:
R
:方法不改变接收器(非破坏性)。W
:方法改变接收器(破坏性)。
28.12.1。 new Array()
new Array(n)
创建一个长度为n
的数组,其中包含n
孔:
new Array()
创建一个空数组。但是,我建议总是使用[]
。
28.12.2。 Array
的静态方法
Array.from<T>(iterable: Iterable<T> | ArrayLike<T>): T[]
[ES6]Array.from<T,U>(iterable: Iterable<T> | ArrayLike<T>, mapFunc: (v: T, k: number) => U, thisArg?: any): U[]
[ES6]将可迭代或类似 Array 的对象转换为 Array。可选地,输入值可以在添加到输出数组之前通过
mapFunc
进行转换。类数组对象具有
.length
和索引属性(粗略地,非负整数的字符串表示):例子:
Array.of<T>(...items: T[]): T[]
[ES6]这个静态方法主要用于
Array
和 Typed Arrays 的子类,它用作自定义数组字面值:
28.12.3。 Array<T>.prototype
的方法
.concat(...items: Array<T[] | T>): T[]
[R,ES3]返回一个新的 Array,它是接收器和所有
items
的串联。非数组参数被视为具有单个元素的数组。.copyWithin(target: number, start: number, end=this.length): this
[W,ES6]将索引范围从
start
到(excel。)end
的元素复制到以target
开头的索引。正确处理重叠。.entries(): Iterable<[number, T]>
[R,ES6]返回[index,element]对上的可迭代。
.every(callback: (value: T, index: number, array: Array<T>) => boolean, thisArg?: any): boolean
[R,ES5]如果
callback
为每个元素和false
返回true
,则返回true
,否则返回。收到false
后立即停止。该方法对应于数学中的通用量化(对于所有,∀
)。.fill(value: T, start=0, end=this.length): this
[W,ES6]将
value
分配给(incl。)start
和(excl。)end
之间的每个索引。.filter(callback: (value: T, index: number, array: Array<T>) => any, thisArg?: any): T[]
[R,ES5]返回一个只包含
callback
返回true
的元素的数组。.find(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): T | undefined
[R,ES6]结果是
predicate
返回true
的第一个元素。如果它永远不会,结果是undefined
。.findIndex(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): number
[R,ES6]结果是
predicate
返回true
的第一个元素的索引。如果它永远不会,结果是-1
。.flat(depth = 1): any[]
[R,ES2019]“展平”数组:它创建数组的副本,其中嵌套数组中的值都出现在顶层。参数
depth
控制.flat()
查找非数组值的深度。.flatMap<U>(callback: (value: T, index: number, array: T[]) => U|Array<U>, thisValue?: any): U[]
[R,ES2019]通过为原始 Array 的每个元素调用
callback()
并连接它返回的 Arrays 来生成结果。.forEach(callback: (value: T, index: number, array: Array<T>) => void, thisArg?: any): void
[R,ES5]为每个元素调用
callback
。.includes(searchElement: T, fromIndex=0): boolean
[R,ES2016]如果接收器具有值为
searchElement
和false
的元素,则返回true
。搜索从索引fromIndex
开始。.indexOf(searchElement: T, fromIndex=0): number
[R,ES5]返回严格等于
searchElement
的第一个元素的索引。如果没有这样的元素,则返回-1
。开始在索引fromIndex
搜索,然后访问更高的索引。.join(separator = ','): string
[R,ES1]通过连接所有元素的字符串表示形式创建一个字符串,用
separator
分隔它们。.keys(): Iterable<number>
[R,ES6]返回接收器的键上的可迭代。
.lastIndexOf(searchElement: T, fromIndex=this.length-1): number
[R,ES5]返回严格等于
searchElement
的最后一个元素的索引。如果没有这样的元素,则返回-1
。开始在索引fromIndex
搜索,然后访问较低的索引。.map<U>(mapFunc: (value: T, index: number, array: Array<T>) => U, thisArg?: any): U[]
[R,ES5]返回一个新的 Array,其中每个元素都是
mapFunc
应用于接收器的相应元素的结果。.pop(): T | undefined
[W,ES3]删除并返回接收器的最后一个元素。也就是说,它将接收器的末尾视为堆栈。与
.push()
相反。.push(...items: T[]): number
[W,ES3]在接收器的末尾添加零个或多个
items
。也就是说,它将接收器的末尾视为堆栈。返回值是更改后接收器的长度。与.pop()
相反。.reduce<U>(callback: (accumulator: U, element: T, index: number, array: T[]) => U, init?: U): U
[R,ES5]此方法生成接收器的摘要:它将所有 Array 元素提供给
callback
,它将当前中间结果(在参数accumulator
中)与当前 Array 元素组合并返回下一个accumulator
:.reduce()
的结果是访问所有 Array 元素后callback
的最后结果。如果未提供
init
,则使用索引 0 处的 Array 元素。.reduceRight<U>(callback: (accumulator: U, element: T, index: number, array: T[]) => U, init?: U): U
[R,ES5]像
.reduce()
一样工作,但是从最后一个元素开始向后访问 Array 元素。.reverse(): this
[W,ES1]重新排列接收器的元素,使它们的顺序相反,然后返回接收器。
.shift(): T | undefined
[W,ES3]删除并返回接收器的第一个元素。与
.unshift()
相反。.slice(start=0, end=this.length): T[]
[R,ES3]返回一个新的 Array,包含接收器的元素,其索引在(incl。)
start
和(excl。)end
之间。.some(callback: (value: T, index: number, array: Array<T>) => boolean, thisArg?: any): boolean
[R,ES5]如果
callback
为至少一个元素返回true
,则返回true
,否则返回false
。收到true
后立即停止。该方法对应于数学中的存在量化(存在,∃
)。.sort(compareFunc?: (a: T, b: T) => number): this
[W,ES1]对接收器进行排序并将其返回。从 ECMAScript 2019 开始,保证排序是稳定的:如果通过排序认为元素相等,那么排序不会改变这些元素的顺序(相对于彼此)。
默认情况下,它对元素的字符串表示进行排序。它按字典顺序并根据字符的代码单元值(字符代码)执行:
您可以通过
compareFunc
自定义排序顺序,它会返回一个数字:- 否定如果
a < b
- 如果
a === b
为零 - 如果
a > b
为正
排序数字的伎俩(有数字溢出的风险):
- 否定如果
.splice(start: number, deleteCount=this.length-start, ...items: T[]): T[]
[W,ES3]在索引
start
处,它删除deleteCount
元素并插入items
。它返回已删除的元素。.toString(): string
[R,ES1]返回一个字符串,其中包含所有元素的字符串,以逗号分隔。
.unshift(...items: T[]): number
[W,ES3]在接收器的开头插入
items
并在此修改后返回其长度。.values(): Iterable<T>
[R,ES6]返回接收器值的可迭代。
28.12.4。来源
测验
参见测验应用程序。