Array).lengthfor-of 和数组 [ES6]for-of:迭代元素for-of:迭代索引for-of:迭代 [索引,元素] 对...) 将可迭代对象转换为数组Array.from() 将可迭代对象和类数组对象转换为数组.find(), .map(), .filter(), 等).find(), .findIndex().map():复制并为元素赋予新值.flatMap():映射到零个或多个值.filter():仅保留部分元素.reduce():从数组中派生值(高级).sort():对数组进行排序Arraynew Array()Array 的静态方法Array.prototype 的方法JavaScript 数组是一种非常灵活的数据结构,可用作列表、堆栈、队列、元组(例如,对)等。
一些与数组相关的操作会破坏性地更改数组。其他操作则以非破坏性的方式生成新的数组,并将更改应用于原始内容的副本。
创建数组、读取和写入元素
// Creating an Array
const arr = ['a', 'b', 'c']; // Array literal
assert.deepEqual(
arr,
[ // Array literal
'a',
'b',
'c', // trailing commas are ignored
]
);
// Reading elements
assert.equal(
arr[0], 'a' // negative indices don’t work
);
assert.equal(
arr.at(-1), 'c' // negative indices work
);
// Writing an element
arr[0] = 'x';
assert.deepEqual(
arr, ['x', 'b', 'c']
);数组的长度
const arr = ['a', 'b', 'c'];
assert.equal(
arr.length, 3 // number of elements
);
arr.length = 1; // removing elements
assert.deepEqual(
arr, ['a']
);
arr[arr.length] = 'b'; // adding an element
assert.deepEqual(
arr, ['a', 'b']
);通过 .push() 破坏性地添加元素
const arr = ['a', 'b'];
arr.push('c'); // adding an element
assert.deepEqual(
arr, ['a', 'b', 'c']
);
// Pushing Arrays (used as arguments via spreading (...)):
arr.push(...['d', 'e']);
assert.deepEqual(
arr, ['a', 'b', 'c', 'd', 'e']
);通过展开运算符 (...) 以非破坏性的方式添加元素
const arr1 = ['a', 'b'];
const arr2 = ['c'];
assert.deepEqual(
[...arr1, ...arr2, 'd', 'e'],
['a', 'b', 'c', 'd', 'e']
);清空数组(删除所有元素)
// Destructive – affects everyone referring to the Array:
const arr1 = ['a', 'b', 'c'];
arr1.length = 0;
assert.deepEqual(
arr1, []
);
// Non-destructive – does not affect others referring to the Array:
let arr2 = ['a', 'b', 'c'];
arr2 = [];
assert.deepEqual(
arr2, []
);循环遍历元素
const arr = ['a', 'b', 'c'];
for (const value of arr) {
console.log(value);
}
// Output:
// 'a'
// 'b'
// 'c'循环遍历索引-值对
const arr = ['a', 'b', 'c'];
for (const [index, value] of arr.entries()) {
console.log(index, value);
}
// Output:
// 0, 'a'
// 1, 'b'
// 2, 'c'当我们无法使用数组字面量时(例如,因为我们事先不知道它们的长度或它们太大),创建和填充数组
const four = 4;
// Empty Array that we’ll fill later
assert.deepEqual(
new Array(four),
[ , , , ,] // four holes; last comma is ignored
);
// An Array filled with a primitive value
assert.deepEqual(
new Array(four).fill(0),
[0, 0, 0, 0]
);
// An Array filled with objects
// Why not .fill()? We’d get single object, shared multiple times.
assert.deepEqual(
Array.from({length: four}, () => ({})),
[{}, {}, {}, {}]
);
// A range of integers
assert.deepEqual(
Array.from({length: four}, (_, i) => i),
[0, 1, 2, 3]
);本节简要概述了数组 API。本章末尾有更全面的快速参考。
从现有数组派生新数组
> ['■','●','▲'].slice(1, 3)
['●','▲']
> ['■','●','■'].filter(x => x==='■')
['■','■']
> ['▲','●'].map(x => x+x)
['▲▲','●●']
> ['▲','●'].flatMap(x => [x,x])
['▲','▲','●','●']删除给定索引处的数组元素
// .filter(): remove non-destructively
const arr1 = ['■','●','▲'];
assert.deepEqual(
arr1.filter((_, index) => index !== 1),
['■','▲']
);
assert.deepEqual(
arr1, ['■','●','▲'] // unchanged
);
// .splice(): remove destructively
const arr2 = ['■','●','▲'];
arr2.splice(1, 1); // start at 1, delete 1 element
assert.deepEqual(
arr2, ['■','▲'] // changed
);计算数组的摘要
> ['■','●','▲'].some(x => x==='●')
true
> ['■','●','▲'].every(x => x==='●')
false
> ['■','●','▲'].join('-')
'■-●-▲'
> ['■','▲'].reduce((result,x) => result+x, '●')
'●■▲'
> ['■','▲'].reduceRight((result,x) => result+x, '●')
'●▲■'反转和填充
// .reverse() changes and returns `arr`
const arr = ['■','●','▲'];
assert.deepEqual(
arr.reverse(), arr
);
// `arr` was changed:
assert.deepEqual(
arr, ['▲','●','■']
);
// .fill() works the same way:
assert.deepEqual(
['■','●','▲'].fill('●'),
['●','●','●']
);.sort() 也会修改数组并返回它
// By default, string representations of the Array elements
// are sorted lexicographically:
assert.deepEqual(
[200, 3, 10].sort(),
[10, 200, 3]
);
// Sorting can be customized via a callback:
assert.deepEqual(
[200, 3, 10].sort((a,b) => a - b), // sort numerically
[ 3, 10, 200 ]
);查找数组元素
> ['■','●','■'].includes('■')
true
> ['■','●','■'].indexOf('■')
0
> ['■','●','■'].lastIndexOf('■')
2
> ['■','●','■'].find(x => x==='■')
'■'
> ['■','●','■'].findIndex(x => x==='■')
0在开头或结尾添加或删除元素
// Adding and removing at the start
const arr1 = ['■','●'];
arr1.unshift('▲');
assert.deepEqual(
arr1, ['▲','■','●']
);
arr1.shift();
assert.deepEqual(
arr1, ['■','●']
);
// Adding and removing at the end
const arr2 = ['■','●'];
arr2.push('▲');
assert.deepEqual(
arr2, ['■','●','▲']
);
arr2.pop();
assert.deepEqual(
arr2, ['■','●']
);在 JavaScript 中有两种使用数组的方式
在实践中,这两种方式经常混合使用。
值得注意的是,序列数组非常灵活,我们可以将它们用作(传统的)数组、堆栈和队列。我们稍后会看到如何做到这一点。
创建数组的最佳方式是通过数组字面量
const arr = ['a', 'b', 'c'];数组字面量以方括号 [] 开始和结束。它创建一个包含三个元素的数组:'a'、'b' 和 'c'。
数组字面量中允许使用尾随逗号,并且会被忽略
const arr = [
'a',
'b',
'c',
];要读取数组元素,我们将索引放在方括号中(索引从零开始)
const arr = ['a', 'b', 'c'];
assert.equal(arr[0], 'a');要更改数组元素,我们将值赋给带有索引的数组
const arr = ['a', 'b', 'c'];
arr[0] = 'x';
assert.deepEqual(arr, ['x', 'b', 'c']);数组索引的范围为 32 位(不包括最大长度):[0, 232−1)
.length每个数组都有一个属性 .length,可用于读取和更改 (!) 数组中元素的数量。
数组的长度始终是最高索引加一
> const arr = ['a', 'b'];
> arr.length
2如果我们在长度索引处写入数组,则会追加一个元素
> arr[arr.length] = 'c';
> arr
[ 'a', 'b', 'c' ]
> arr.length
3(破坏性地)追加元素的另一种方法是通过数组方法 .push()
> arr.push('d');
> arr
[ 'a', 'b', 'c', 'd' ]如果我们设置 .length,我们就是在通过删除元素来修剪数组
> arr.length = 1;
> arr
[ 'a' ] 练习:通过
.push() 删除空行
exercises/arrays/remove_empty_lines_push_test.mjs
几种数组方法支持负索引。如果索引为负数,则将其添加到数组的长度以生成可用的索引。因此,以下两次调用 .slice() 是等效的:它们都从最后一个元素开始复制 arr。
> const arr = ['a', 'b', 'c'];
> arr.slice(-1)
[ 'c' ]
> arr.slice(arr.length - 1)
[ 'c' ].at():读取单个元素(支持负索引)[ES2022]数组方法 .at() 返回给定索引处的元素。它支持正负索引(-1 指的是最后一个元素,-2 指的是倒数第二个元素,等等)
> ['a', 'b', 'c'].at(0)
'a'
> ['a', 'b', 'c'].at(-1)
'c'相反,方括号运算符 [] 不支持负索引(并且不能更改,因为这会破坏现有代码)。它将它们解释为非元素属性的键
const arr = ['a', 'b', 'c'];
arr[-1] = 'non-element property';
// The Array elements didn’t change:
assert.deepEqual(
Array.from(arr), // copy just the Array elements
['a', 'b', 'c']
);
assert.equal(
arr[-1], 'non-element property'
);要清空数组,我们可以将其 .length 设置为零
const arr = ['a', 'b', 'c'];
arr.length = 0;
assert.deepEqual(arr, []);或者我们可以将一个新的空数组赋给存储数组的变量
let arr = ['a', 'b', 'c'];
arr = [];
assert.deepEqual(arr, []);后一种方法的优点是不会影响指向同一数组的其他位置。但是,如果我们确实想为每个人重置共享数组,那么我们需要前一种方法。
在数组字面量中,展开元素由三个点 (...) 后跟一个表达式组成。它会导致表达式被求值,然后对其进行迭代。每个迭代值都成为一个额外的数组元素 - 例如
> const iterable = ['b', 'c'];
> ['a', ...iterable, 'd']
[ 'a', 'b', 'c', 'd' ]这意味着我们可以使用展开运算符来创建数组的副本并将可迭代对象转换为数组
const original = ['a', 'b', 'c'];
const copy = [...original];
const iterable = original.keys();
assert.deepEqual(
[...iterable], [0, 1, 2]
);但是,对于之前的两种用例,我发现 Array.from() 更具描述性,并且更喜欢它
const copy2 = Array.from(original);
assert.deepEqual(
Array.from(original.keys()), [0, 1, 2]
);展开运算符对于将数组(和其他可迭代对象)连接成数组也很方便
const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];
const concatenated = [...arr1, ...arr2, 'e'];
assert.deepEqual(
concatenated,
['a', 'b', 'c', 'd', 'e']);由于展开运算符使用迭代,因此仅当值是可迭代的时才有效
> [...'abc'] // strings are iterable
[ 'a', 'b', 'c' ]
> [...123]
TypeError: 123 is not iterable
> [...undefined]
TypeError: undefined is not iterable 展开运算符和
Array.from() 生成浅拷贝
通过展开运算符或 Array.from() 复制数组是浅拷贝:我们在新数组中获得了新的条目,但值与原始数组共享。浅拷贝的后果在§28.4 “展开到对象字面量 (...) [ES2018]” 中进行了演示。
方法 .keys() 列出数组的索引
const arr = ['a', 'b'];
assert.deepEqual(
Array.from(arr.keys()), // (A)
[0, 1]);.keys() 返回一个可迭代对象。在 A 行中,我们将该可迭代对象转换为数组。
列出数组索引与列出属性不同。前者生成数字;后者生成字符串化的数字(以及非索引属性键)
const arr = ['a', 'b'];
arr.prop = true;
assert.deepEqual(
Object.keys(arr),
['0', '1', 'prop']);方法 .entries() 将数组的内容列为 [索引,元素] 对
const arr = ['a', 'b'];
assert.deepEqual(
Array.from(arr.entries()),
[[0, 'a'], [1, 'b']]);以下是检查值是否为数组的两种方法
> [] instanceof Array
true
> Array.isArray([])
trueinstanceof 通常没问题。如果值可能来自另一个领域,则需要 Array.isArray()。粗略地说,领域是 JavaScript 全局作用域的实例。某些领域彼此隔离(例如,浏览器中的Web Workers),但也有一些领域可以在它们之间移动数据 - 例如,浏览器中同源的 iframe。x instanceof Array 检查 x 的原型链,因此如果 x 是来自另一个领域的数组,则返回 false。
typeof 将数组归类为对象
> typeof []
'object'for-of 和数组 [ES6]我们在这本书的前面已经遇到过 for-of 循环。本节简要回顾了如何将其用于数组。
for-of:迭代元素以下 for-of 循环迭代数组的元素
for (const element of ['a', 'b']) {
console.log(element);
}
// Output:
// 'a'
// 'b'for-of:迭代索引此 for-of 循环迭代数组的索引
for (const element of ['a', 'b'].keys()) {
console.log(element);
}
// Output:
// 0
// 1for-of:迭代 [索引,元素] 对以下 for-of 循环迭代 [索引,元素] 对。解构(稍后描述)为我们在 for-of 的头部设置 index 和 element 提供了方便的语法。
for (const [index, element] of ['a', 'b'].entries()) {
console.log(index, element);
}
// Output:
// 0, 'a'
// 1, 'b'某些使用数组的操作只需要最基本的条件:值必须是类数组的。类数组值是具有以下属性的对象
.length:保存类数组对象的长度。[0]:保存索引 0 处的元素(等等)。请注意,如果我们使用数字作为属性名称,则它们始终会被强制转换为字符串。因此,[0] 检索键为 '0' 的属性的值。例如,Array.from() 接受类数组对象并将它们转换为数组
// If we omit .length, it is interpreted as 0
assert.deepEqual(
Array.from({}),
[]);
assert.deepEqual(
Array.from({length:2, 0:'a', 1:'b'}),
[ 'a', 'b' ]);类数组对象的 TypeScript 接口是
interface ArrayLike<T> {
length: number;
[n: number]: T;
} 类数组对象在现代 JavaScript 中相对少见
类数组对象在 ES6 之前很常见;现在我们不经常看到它们了。
将可迭代对象和类数组值转换为数组有两种常用方法
Array.from()我更喜欢后者——我觉得它更易于理解。
...) 将可迭代对象转换为数组在数组字面量中,通过 ... 展开运算符可以将任何可迭代对象转换为一系列数组元素。例如
// Get an Array-like collection from a web browser’s DOM
const domCollection = document.querySelectorAll('a');
// Alas, the collection is missing many Array methods
assert.equal('map' in domCollection, false);
// Solution: convert it to an Array
const arr = [...domCollection];
assert.deepEqual(
arr.map(x => x.href),
['https://2ality.com', 'https://exploring.javascript.ac.cn']);这种转换之所以有效,是因为 DOM 集合是可迭代的。
Array.from() 将可迭代对象和类数组对象转换为数组Array.from() 可以以两种模式使用。
Array.from() 的模式 1:转换第一种模式具有以下类型签名
.from<T>(iterable: Iterable<T> | ArrayLike<T>): T[]接口 Iterable 在关于同步迭代的章节中 展示。接口 ArrayLike 在本节前面 出现过。
使用单个参数,Array.from() 可以将任何可迭代对象或类数组对象转换为数组
> Array.from(new Set(['a', 'b']))
[ 'a', 'b' ]
> Array.from({length: 2, 0:'a', 1:'b'})
[ 'a', 'b' ]Array.from() 的模式 2:转换和映射Array.from() 的第二种模式涉及两个参数
.from<T, U>(
iterable: Iterable<T> | ArrayLike<T>,
mapFunc: (v: T, i: number) => U,
thisArg?: any)
: U[]在这种模式下,Array.from() 会执行以下操作
iterable。mapFunc。可选参数 thisArg 为 mapFunc 指定 this。mapFunc 应用于每个迭代值。换句话说:我们将类型为 T 的元素的可迭代对象转换为类型为 U 的元素的数组。
这是一个例子
> Array.from(new Set(['a', 'b']), x => x + x)
[ 'aa', 'bb' ]创建数组的最佳方法是使用数组字面量。但是,我们不能总是使用它:数组可能太大,我们在开发过程中可能不知道它的长度,或者我们可能希望保持它的长度灵活。然后,我建议使用以下技术来创建和填充数组。
> new Array(3)
[ , , ,]请注意,结果有三个 空洞(空槽)——数组字面量中的最后一个逗号总是被忽略。
> new Array(3).fill(0)
[0, 0, 0]注意:如果我们对对象使用 .fill(),则每个数组元素都将引用此对象(共享它)。
const arr = new Array(3).fill({});
arr[0].prop = true;
assert.deepEqual(
arr, [
{prop: true},
{prop: true},
{prop: true},
]);下一小节 将解释如何解决此问题。
> new Array(3).fill(0)
[0, 0, 0]对于大型数组,临时数组可能会消耗大量内存。以下方法没有这个缺点,但可读性较差
> Array.from({length: 3}, () => ({}))
[{}, {}, {}]我们使用的是一个临时的 类数组对象,而不是临时数组。
function createRange(start, end) {
return Array.from({length: end-start}, (_, i) => i+start);
}
assert.deepEqual(
createRange(2, 5),
[2, 3, 4]);以下是另一种创建从零开始的整数范围的技巧,但有点 hacky
/** Returns an iterable */
function createRange(end) {
return new Array(end).keys();
}
assert.deepEqual(
Array.from(createRange(4)),
[0, 1, 2, 3]);之所以可行,是因为 .keys() 将 空洞 视为 undefined 元素并列出它们的索引。
在处理整数或浮点数数组时,我们应该考虑 类型化数组,它们就是为此目的而创建的。
JavaScript 没有真正的多维数组;我们需要求助于元素为数组的数组
function initMultiArray(...dimensions) {
function initMultiArrayRec(dimIndex) {
if (dimIndex >= dimensions.length) {
return 0;
} else {
const dim = dimensions[dimIndex];
const arr = [];
for (let i=0; i<dim; i++) {
arr.push(initMultiArrayRec(dimIndex+1));
}
return arr;
}
}
return initMultiArrayRec(0);
}
const arr = initMultiArray(4, 3, 2);
arr[3][2][1] = 'X'; // last in each dimension
assert.deepEqual(arr, [
[ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ],
[ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ],
[ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ],
[ [ 0, 0 ], [ 0, 0 ], [ 0, 'X' ] ],
]);在本节中,我们将研究在使用数组时不常遇到的现象。
你可能认为数组元素很特殊,因为我们是通过数字访问它们的。但是用于执行此操作的方括号运算符 [] 与用于访问属性的运算符相同。它会将任何值(符号除外)强制转换为字符串。因此,数组元素(几乎)是普通属性(A 行),我们使用数字还是字符串作为索引无关紧要(B 行和 C 行)
const arr = ['a', 'b'];
arr.prop = 123;
assert.deepEqual(
Object.keys(arr),
['0', '1', 'prop']); // (A)
assert.equal(arr[0], 'a'); // (B)
assert.equal(arr['0'], 'a'); // (C)更令人困惑的是,这只是语言规范定义事物的方式(如果你愿意,可以称之为 JavaScript 的理论)。大多数 JavaScript 引擎在底层进行了优化,并确实使用实际的整数来访问数组元素(如果你愿意,可以称之为 JavaScript 的实践)。
用于数组元素的属性键(字符串!)称为 索引。如果将字符串 str 转换为 32 位无符号整数再转换回来,结果是原始值,则该字符串就是一个索引。写成公式就是
ToString(ToUint32(str)) === str
在列出属性键时,索引的处理方式很特殊——它们总是排在第一位,并且像数字一样排序('2' 在 '10' 之前)
const arr = [];
arr.prop = true;
arr[1] = 'b';
arr[0] = 'a';
assert.deepEqual(
Object.keys(arr),
['0', '1', 'prop']);请注意,.length、.entries() 和 .keys() 将数组索引视为数字,并忽略非索引属性
assert.equal(arr.length, 2);
assert.deepEqual(
Array.from(arr.keys()), [0, 1]);
assert.deepEqual(
Array.from(arr.entries()), [[0, 'a'], [1, 'b']]);我们使用 Array.from() 将 .keys() 和 .entries() 返回的可迭代对象转换为数组。
我们在 JavaScript 中区分两种数组
arr 中的所有索引 i(0 ≤ i < arr.length)都存在,则该数组是密集数组。也就是说,索引形成一个连续的范围。JavaScript 中的数组可以是稀疏的,因为数组实际上是从索引到值的字典。
建议:避免空洞
到目前为止,我们只见过密集数组,实际上建议避免空洞:它们会使我们的代码更加复杂,并且数组方法对它们的处理方式也不一致。此外,JavaScript 引擎会优化密集数组,使其速度更快。
我们可以在分配元素时跳过索引来创建空洞
const arr = [];
arr[0] = 'a';
arr[2] = 'c';
assert.deepEqual(Object.keys(arr), ['0', '2']); // (A)
assert.equal(0 in arr, true); // element
assert.equal(1 in arr, false); // hole在 A 行中,我们使用的是 Object.keys(),因为 arr.keys() 会将空洞视为 undefined 元素,不会显示它们。
另一种创建空洞的方法是在数组字面量中跳过元素
const arr = ['a', , 'c'];
assert.deepEqual(Object.keys(arr), ['0', '2']);我们还可以删除数组元素
const arr = ['a', 'b', 'c'];
assert.deepEqual(Object.keys(arr), ['0', '1', '2']);
delete arr[1];
assert.deepEqual(Object.keys(arr), ['0', '2']);唉,数组操作处理空洞的方式有很多种。
一些数组操作会删除空洞
> ['a',,'b'].filter(x => true)
[ 'a', 'b' ]一些数组操作会忽略空洞
> ['a', ,'a'].every(x => x === 'a')
true一些数组操作会忽略但保留空洞
> ['a',,'b'].map(x => 'c')
[ 'c', , 'c' ]一些数组操作会将空洞视为 undefined 元素
> Array.from(['a',,'b'], x => x)
[ 'a', undefined, 'b' ]
> Array.from(['a',,'b'].entries())
[[0, 'a'], [1, undefined], [2, 'b']]Object.keys() 的工作方式与 .keys() 不同(字符串与数字,空洞没有键)
> Array.from(['a',,'b'].keys())
[ 0, 1, 2 ]
> Object.keys(['a',,'b'])
[ '0', '2' ]这里没有规则可循。如果数组操作如何处理空洞很重要,最好的方法是在控制台中进行快速测试。
JavaScript 的 Array 非常灵活,更像是数组、堆栈和队列的组合。本节探讨添加和删除数组元素的方法。大多数操作都可以破坏性地(修改数组)和非破坏性地(生成修改后的副本)执行。
在以下代码中,我们破坏性地将单个元素前置到 arr1,并将数组前置到 arr2
const arr1 = ['a', 'b'];
arr1.unshift('x', 'y'); // prepend single elements
assert.deepEqual(arr1, ['x', 'y', 'a', 'b']);
const arr2 = ['a', 'b'];
arr2.unshift(...['x', 'y']); // prepend Array
assert.deepEqual(arr2, ['x', 'y', 'a', 'b']);展开运算符允许我们将数组 unshift 到 arr2 中。
非破坏性前置是通过展开元素完成的
const arr1 = ['a', 'b'];
assert.deepEqual(
['x', 'y', ...arr1], // prepend single elements
['x', 'y', 'a', 'b']);
assert.deepEqual(arr1, ['a', 'b']); // unchanged!
const arr2 = ['a', 'b'];
assert.deepEqual(
[...['x', 'y'], ...arr2], // prepend Array
['x', 'y', 'a', 'b']);
assert.deepEqual(arr2, ['a', 'b']); // unchanged!在以下代码中,我们破坏性地将单个元素追加到 arr1,并将数组追加到 arr2
const arr1 = ['a', 'b'];
arr1.push('x', 'y'); // append single elements
assert.deepEqual(arr1, ['a', 'b', 'x', 'y']);
const arr2 = ['a', 'b'];
arr2.push(...['x', 'y']); // (A) append Array
assert.deepEqual(arr2, ['a', 'b', 'x', 'y']);展开运算符 (...) 允许我们将数组 push 到 arr2 中(A 行)。
非破坏性追加是通过展开元素完成的
const arr1 = ['a', 'b'];
assert.deepEqual(
[...arr1, 'x', 'y'], // append single elements
['a', 'b', 'x', 'y']);
assert.deepEqual(arr1, ['a', 'b']); // unchanged!
const arr2 = ['a', 'b'];
assert.deepEqual(
[...arr2, ...['x', 'y']], // append Array
['a', 'b', 'x', 'y']);
assert.deepEqual(arr2, ['a', 'b']); // unchanged!以下是删除数组元素的三种破坏性方法
// Destructively remove first element:
const arr1 = ['a', 'b', 'c'];
assert.equal(arr1.shift(), 'a');
assert.deepEqual(arr1, ['b', 'c']);
// Destructively remove last element:
const arr2 = ['a', 'b', 'c'];
assert.equal(arr2.pop(), 'c');
assert.deepEqual(arr2, ['a', 'b']);
// Remove one or more elements anywhere:
const arr3 = ['a', 'b', 'c', 'd'];
assert.deepEqual(arr3.splice(1, 2), ['b', 'c']);
assert.deepEqual(arr3, ['a', 'd']);.splice() 在 本章末尾的快速参考 中有更详细的介绍。
通过剩余元素进行解构允许我们以非破坏性的方式从数组的开头删除元素(解构将在 后面 介绍)。
const arr1 = ['a', 'b', 'c'];
// Ignore first element, extract remaining elements
const [, ...arr2] = arr1;
assert.deepEqual(arr2, ['b', 'c']);
assert.deepEqual(arr1, ['a', 'b', 'c']); // unchanged!唉,剩余元素必须位于数组的最后。因此,我们只能使用它来提取后缀。
练习:通过数组实现队列
exercises/arrays/queue_via_array_test.mjs
.find()、.map()、.filter() 等)在本节中,我们将研究用于迭代数组和转换数组的数组方法。
所有迭代和转换方法都使用回调。前者将其所有迭代值都提供给其回调;后者询问其回调如何转换数组。
这些回调具有如下所示的类型签名
callback: (value: T, index: number, array: Array<T>) => boolean也就是说,回调会获得三个参数(它可以忽略其中的任何一个)
value 是最重要的一个。此参数保存当前正在处理的迭代值。index 还可以告诉回调迭代值的索引是什么。array 指向当前数组(方法调用的接收者)。某些算法需要引用整个数组——例如,在其中搜索答案。此参数允许我们为这些算法编写可重用的回调。回调预期返回什么取决于它被传递给哪个方法。可能性包括
.map() 使用其回调返回的值填充其结果
> ['a', 'b', 'c'].map(x => x + x)
[ 'aa', 'bb', 'cc' ].find() 返回其回调返回 true 的第一个数组元素
> ['a', 'bb', 'ccc'].find(str => str.length >= 2)
'bb'这两种方法将在后面详细介绍。
.find()、.findIndex().find() 返回其回调返回真值(truthy value)的第一个元素(如果找不到任何元素,则返回 undefined)
> [6, -5, 8].find(x => x < 0)
-5
> [6, 5, 8].find(x => x < 0)
undefined.findIndex() 返回其回调返回真值(truthy value)的第一个元素的索引(如果找不到任何元素,则返回 -1)
> [6, -5, 8].findIndex(x => x < 0)
1
> [6, 5, 8].findIndex(x => x < 0)
-1.findIndex() 可以实现如下
function findIndex(arr, callback) {
for (const [i, x] of arr.entries()) {
if (callback(x, i, arr)) {
return i;
}
}
return -1;
}.map():复制并为元素赋予新值.map() 返回接收者的修改副本。副本的元素是将 map 的回调应用于接收者的元素的结果。
通过示例更容易理解这一切
> [1, 2, 3].map(x => x * 3)
[ 3, 6, 9 ]
> ['how', 'are', 'you'].map(str => str.toUpperCase())
[ 'HOW', 'ARE', 'YOU' ]
> [true, true, true].map((_x, index) => index)
[ 0, 1, 2 ].map() 可以实现如下
function map(arr, mapFunc) {
const result = [];
for (const [i, x] of arr.entries()) {
result.push(mapFunc(x, i, arr));
}
return result;
} 练习:通过
.map() 对行进行编号
exercises/arrays/number_lines_test.mjs
.flatMap():映射到零个或多个值Array<T>.prototype.flatMap() 的类型签名是
.flatMap<U>(
callback: (value: T, index: number, array: T[]) => U|Array<U>,
thisValue?: any
): U[].map() 和 .flatMap() 都将函数 callback 作为参数,该函数控制如何将输入数组转换为输出数组
.map() 时,每个输入数组元素都会转换为一个输出元素。也就是说,callback 返回一个值。.flatMap() 时,每个输入数组元素都会转换为零个或多个输出元素。也就是说,callback 返回一个值数组(它也可以返回非数组值,但这很少见)。这是 .flatMap() 的实际应用
> ['a', 'b', 'c'].flatMap(x => [x,x])
[ 'a', 'a', 'b', 'b', 'c', 'c' ]
> ['a', 'b', 'c'].flatMap(x => [x])
[ 'a', 'b', 'c' ]
> ['a', 'b', 'c'].flatMap(x => [])
[]在探讨如何实现此方法之前,我们将先考虑用例。
数组方法 .map() 的结果始终与其调用它的数组具有相同的长度。也就是说,它的回调不能跳过它不感兴趣的数组元素。.flatMap() 的这种能力在下一个示例中很有用。
我们将使用以下函数 processArray() 创建一个数组,然后我们将通过 .flatMap() 对其进行过滤和映射
function processArray(arr, callback) {
return arr.map(x => {
try {
return { value: callback(x) };
} catch (e) {
return { error: e };
}
});
}接下来,我们通过 processArray() 创建一个数组 results
const results = processArray([1, -5, 6], throwIfNegative);
assert.deepEqual(results, [
{ value: 1 },
{ error: new Error('Illegal value: -5') },
{ value: 6 },
]);
function throwIfNegative(value) {
if (value < 0) {
throw new Error('Illegal value: '+value);
}
return value;
}现在,我们可以使用 .flatMap() 从 results 中仅提取值或仅提取错误
const values = results.flatMap(
result => result.value ? [result.value] : []);
assert.deepEqual(values, [1, 6]);
const errors = results.flatMap(
result => result.error ? [result.error] : []);
assert.deepEqual(errors, [new Error('Illegal value: -5')]);数组方法 .map() 将每个输入数组元素映射到一个输出元素。但是,如果我们想将其映射到多个输出元素呢?
在以下示例中,这变得很有必要
> stringsToCodePoints(['many', 'a', 'moon'])
['m', 'a', 'n', 'y', 'a', 'm', 'o', 'o', 'n']我们想将字符串数组转换为 Unicode 字符(码位)数组。以下函数通过 .flatMap() 实现这一点。
function stringsToCodePoints(strs) {
return strs.flatMap(str => Array.from(str));
}我们可以按如下方式实现 .flatMap()。注意:此实现比内置版本更简单,例如,内置版本执行更多检查。
function flatMap(arr, mapFunc) {
const result = [];
for (const [index, elem] of arr.entries()) {
const x = mapFunc(elem, index, arr);
// We allow mapFunc() to return non-Arrays
if (Array.isArray(x)) {
result.push(...x);
} else {
result.push(x);
}
}
return result;
} 练习:
.flatMap()
exercises/arrays/convert_to_numbers_test.mjs
exercises/arrays/replace_objects_test.mjs
.filter():仅保留某些元素数组方法 .filter() 返回一个数组,该数组收集回调函数返回真值的所有元素。
例如:
> [-1, 2, 5, -7, 6].filter(x => x >= 0)
[ 2, 5, 6 ]
> ['a', 'b', 'c', 'd'].filter((_x,i) => (i%2)===0)
[ 'a', 'c' ].filter() 可以按如下方式实现:
function filter(arr, filterFunc) {
const result = [];
for (const [i, x] of arr.entries()) {
if (filterFunc(x, i, arr)) {
result.push(x);
}
}
return result;
} 练习:通过
.filter() 删除空行
exercises/arrays/remove_empty_lines_filter_test.mjs
.reduce():从数组派生值(高级)方法 .reduce() 是一个强大的工具,用于计算数组 arr 的“汇总”。汇总可以是任何类型的值:
arr 中所有元素的总和。arr 的副本,其中每个元素都是原始元素的两倍。reduce 在函数式编程中也称为 foldl(“左折叠”),并且在那里很流行。需要注意的是,它可能会使代码难以理解。
.reduce() 具有以下类型签名(在 Array<T> 内部):
.reduce<U>(
callback: (accumulator: U, element: T, index: number, array: T[]) => U,
init?: U)
: UT 是数组元素的类型,U 是汇总的类型。两者可能相同,也可能不同。accumulator 只是“汇总”的另一个名称。
为了计算数组 arr 的汇总,.reduce() 将所有数组元素一次一个地提供给其回调函数:
const accumulator_0 = callback(init, arr[0]);
const accumulator_1 = callback(accumulator_0, arr[1]);
const accumulator_2 = callback(accumulator_1, arr[2]);
// Etc.callback 将先前计算的汇总(存储在其参数 accumulator 中)与当前数组元素组合在一起,并返回下一个 accumulator。.reduce() 的结果是最终的累加器 - callback 访问所有元素后的最后一个结果。
换句话说:callback 完成了大部分工作;.reduce() 只是以一种有用的方式调用它。
我们可以说回调函数将数组元素折叠到累加器中。这就是为什么此操作在函数式编程中称为“折叠”的原因。
让我们看一个 .reduce() 的实际示例:函数 addAll() 计算数组 arr 中所有数字的总和。
function addAll(arr) {
const startSum = 0;
const callback = (sum, element) => sum + element;
return arr.reduce(callback, startSum);
}
assert.equal(addAll([1, 2, 3]), 6); // (A)
assert.equal(addAll([7, -4, 2]), 5);在这种情况下,累加器保存 callback 已经访问过的所有数组元素的总和。
如何从 A 行中的数组派生出结果 6?通过以下对 callback 的调用:
callback(0, 1) --> 1
callback(1, 2) --> 3
callback(3, 3) --> 6
注意:
.reduce() 的参数 init 开始)。callback 的最后一个结果也是 .reduce() 的结果。或者,我们可以通过 for-of 循环来实现 addAll():
function addAll(arr) {
let sum = 0;
for (const element of arr) {
sum = sum + element;
}
return sum;
}很难说这两种实现哪种“更好”:基于 .reduce() 的实现更简洁一些,而基于 for-of 的实现可能更容易理解 - 特别是如果有人不熟悉函数式编程的话。
.reduce() 查找索引以下函数是数组方法 .indexOf() 的实现。它返回给定 searchValue 出现在数组 arr 中的第一个索引。
const NOT_FOUND = -1;
function indexOf(arr, searchValue) {
return arr.reduce(
(result, elem, index) => {
if (result !== NOT_FOUND) {
// We have already found something: don’t change anything
return result;
} else if (elem === searchValue) {
return index;
} else {
return NOT_FOUND;
}
},
NOT_FOUND);
}
assert.equal(indexOf(['a', 'b', 'c'], 'b'), 1);
assert.equal(indexOf(['a', 'b', 'c'], 'x'), -1);.reduce() 的一个限制是我们不能提前结束(在 for-of 循环中,我们可以使用 break)。在这里,我们总是在找到结果后立即返回它。
函数 double(arr) 返回 inArr 的副本,其元素都乘以 2。
function double(inArr) {
return inArr.reduce(
(outArr, element) => {
outArr.push(element * 2);
return outArr;
},
[]);
}
assert.deepEqual(
double([1, 2, 3]),
[2, 4, 6]);我们通过推入初始值 [] 来修改它。double() 的非破坏性、更函数式的版本如下所示:
function double(inArr) {
return inArr.reduce(
// Don’t change `outArr`, return a fresh Array
(outArr, element) => [...outArr, element * 2],
[]);
}
assert.deepEqual(
double([1, 2, 3]),
[2, 4, 6]);此版本更优雅,但速度较慢,并且使用更多内存。
练习:
.reduce()
.reduce() 实现 map():exercises/arrays/map_via_reduce_test.mjs.reduce() 实现 filter():exercises/arrays/filter_via_reduce_test.mjs.reduce() 实现 countMatches():exercises/arrays/count_matches_via_reduce_test.mjs.sort():对数组进行排序.sort() 具有以下类型定义:
sort(compareFunc?: (a: T, b: T) => number): this默认情况下,.sort() 对元素的字符串表示形式进行排序。这些表示形式通过 < 进行比较。此运算符按*字典顺序*进行比较(第一个字符最先比较)。我们可以在对数字进行排序时看到这一点:
> [200, 3, 10].sort()
[ 10, 200, 3 ]在对人类语言字符串进行排序时,需要注意它们是根据其代码单元值(字符代码)进行比较的。
> ['pie', 'cookie', 'éclair', 'Pie', 'Cookie', 'Éclair'].sort()
[ 'Cookie', 'Pie', 'cookie', 'pie', 'Éclair', 'éclair' ]所有不带重音符号的大写字母都在所有不带重音符号的小写字母之前,而所有带重音符号的字母都在所有不带重音符号的小写字母之后。如果我们想对人类语言进行正确的排序,我们可以使用 Intl,JavaScript 国际化 API。
.sort() *就地*排序;它会更改并返回其接收器。
> const arr = ['a', 'c', 'b'];
> arr.sort() === arr
true
> arr
[ 'a', 'b', 'c' ]我们可以通过参数 compareFunc 自定义排序顺序,该参数必须返回一个数字,该数字:
a < b,则为负数。a === b,则为零。a > b,则为正数。 记住这些规则的技巧
负数*小于*零(等等)。
我们可以使用此辅助函数对数字进行排序:
function compareNumbers(a, b) {
if (a < b) {
return -1;
} else if (a === b) {
return 0;
} else {
return 1;
}
}
assert.deepEqual(
[200, 3, 10].sort(compareNumbers),
[3, 10, 200]);以下是一种快速但不太规范的替代方法。
> [200, 3, 10].sort((a,b) => a - b)
[ 3, 10, 200 ]这种方法的缺点是:
a-b 变为很大的正数或负数,则存在数字溢出或下溢的风险。如果我们想对对象进行排序,我们还需要使用比较函数。例如,以下代码显示了如何按年龄对对象进行排序。
const arr = [ {age: 200}, {age: 3}, {age: 10} ];
assert.deepEqual(
arr.sort((obj1, obj2) => obj1.age - obj2.age),
[{ age: 3 }, { age: 10 }, { age: 200 }] ); 练习:按名称对对象进行排序
exercises/arrays/sort_objects_test.mjs
Array图例:
R:方法不会更改数组(非破坏性)。W:方法会更改数组(破坏性)。new Array()new Array(n) 创建一个长度为 n 的数组,其中包含 n 个空位。
// Trailing commas are always ignored.
// Therefore: number of commas = number of holes
assert.deepEqual(new Array(3), [,,,]);new Array() 创建一个空数组。但是,我建议始终使用 [] 来代替。
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]
将可迭代对象或类数组对象转换为数组。可选地,可以在将输入值添加到输出数组之前,通过 mapFunc 对其进行转换。
示例:
> Array.from(new Set(['a', 'b'])) // iterable
[ 'a', 'b' ]
> Array.from({length: 2, 0:'a', 1:'b'}) // Array-like object
[ 'a', 'b' ]Array.of<T>(...items: T[]): T[] [ES6]
此静态方法主要用于 Array 的子类,在子类中它充当自定义数组字面量。
class MyArray extends Array {}
assert.equal(
MyArray.of('a', 'b') instanceof MyArray, true);Array.prototype 的方法.at(index: number): T | undefined [R, ES2022]
返回索引 index 处的数组元素。如果 index 为负数,则在使用它之前将其添加到 .length 中(-1 变为 this.length-1,等等)。
> ['a', 'b', 'c'].at(0)
'a'
> ['a', 'b', 'c'].at(-1)
'c'.concat(...items: Array<T[] | T>): T[] [R, ES3]
返回一个新数组,该数组是接收器和所有 items 的串联。非数组参数(例如以下示例中的 'b')将被视为具有单个元素的数组。
> ['a'].concat('b', ['c', 'd'])
[ 'a', 'b', 'c', 'd' ].copyWithin(target: number, start: number, end=this.length): this [W, ES6]
将索引范围从(包括)start 到(不包括)end 的元素复制到从 target 开始的索引。重叠部分将被正确处理。
> ['a', 'b', 'c', 'd'].copyWithin(0, 2, 4)
[ 'c', 'd', 'c', 'd' ]如果 start 或 end 为负数,则将 .length 添加到其中。
.entries(): Iterable<[number, T]> [R, ES6]
返回一个迭代器,该迭代器迭代 [索引,元素] 对。
> Array.from(['a', 'b'].entries())
[ [ 0, 'a' ], [ 1, 'b' ] ].every(callback: (value: T, index: number, array: Array<T>) => boolean, thisArg?: any): boolean [R, ES5]
如果 callback 对每个元素都返回真值,则返回 true。否则,它返回 false。一旦收到假值,它就会停止。此方法对应于数学中的全称量词(“对于所有”,∀)。
> [1, 2, 3].every(x => x > 0)
true
> [1, -2, 3].every(x => x > 0)
false相关方法:.some()(“存在”)。
.fill(value: T, start=0, end=this.length): this [W, ES6]
将 value 分配给(包括)start 和(不包括)end 之间的每个索引。
> [0, 1, 2].fill('a')
[ 'a', 'a', 'a' ]注意:不要使用此方法用对象 obj 填充数组;然后每个元素都将引用 obj(共享它)。在这种情况下,最好使用 Array.from()。
.filter(callback: (value: T, index: number, array: Array<T>) => any, thisArg?: any): T[] [R, ES5]
返回一个数组,该数组仅包含 callback 返回真值的元素。
> [1, -2, 3].filter(x => x > 0)
[ 1, 3 ].find(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): T | undefined [R, ES6]
结果是 predicate 返回真值的第一个元素。如果没有这样的元素,则结果为 undefined。
> [1, -2, 3].find(x => x < 0)
-2
> [1, 2, 3].find(x => x < 0)
undefined.findIndex(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): number [R, ES6]
结果是 predicate 返回真值的第一个元素的索引。如果没有这样的元素,则结果为 -1。
> [1, -2, 3].findIndex(x => x < 0)
1
> [1, 2, 3].findIndex(x => x < 0)
-1.flat(depth = 1): any[] [R, ES2019]
“展平”数组:它会深入到嵌套在输入数组内的数组中,并创建一个副本,其中在级别 depth 或更低级别找到的所有值都将移至顶层。
> [ 1,2, [3,4], [[5,6]] ].flat(0) // no change
[ 1, 2, [3,4], [[5,6]] ]
> [ 1,2, [3,4], [[5,6]] ].flat(1)
[1, 2, 3, 4, [5,6]]
> [ 1,2, [3,4], [[5,6]] ].flat(2)
[1, 2, 3, 4, 5, 6].flatMap<U>(callback: (value: T, index: number, array: T[]) => U|Array<U>, thisValue?: any): U[] [R, ES2019]
结果是通过为原始数组的每个元素调用 callback() 并连接它返回的数组而生成的。
> ['a', 'b', 'c'].flatMap(x => [x,x])
[ 'a', 'a', 'b', 'b', 'c', 'c' ]
> ['a', 'b', 'c'].flatMap(x => [x])
[ 'a', 'b', 'c' ]
> ['a', 'b', 'c'].flatMap(x => [])
[].forEach(callback: (value: T, index: number, array: Array<T>) => void, thisArg?: any): void [R, ES5]
为每个元素调用 callback。
['a', 'b'].forEach((x, i) => console.log(x, i))
// Output:
// 'a', 0
// 'b', 1for-of 循环通常是更好的选择:它速度更快,支持 break,并且可以迭代任意可迭代对象。
.includes(searchElement: T, fromIndex=0): boolean [R, ES2016]
如果接收器有一个值为 searchElement 的元素,则返回 true,否则返回 false。搜索从索引 fromIndex 开始。
> [0, 1, 2].includes(1)
true
> [0, 1, 2].includes(5)
false.indexOf(searchElement: T, fromIndex=0): number [R, ES5]
返回严格等于 searchElement 的第一个元素的索引。如果没有这样的元素,则返回 -1。从索引 fromIndex 开始搜索,接下来访问更高的索引。
> ['a', 'b', 'a'].indexOf('a')
0
> ['a', 'b', 'a'].indexOf('a', 1)
2
> ['a', 'b', 'a'].indexOf('c')
-1.join(separator = ','): string [R, ES1]
通过连接所有元素的字符串表示形式并使用 separator 将它们分隔开来创建一个字符串。
> ['a', 'b', 'c'].join('##')
'a##b##c'
> ['a', 'b', 'c'].join()
'a,b,c'.keys(): Iterable<number> [R, ES6]
返回一个迭代器,该迭代器迭代接收器的键。
> Array.from(['a', 'b'].keys())
[ 0, 1 ].lastIndexOf(searchElement: T, fromIndex=this.length-1): number [R, ES5]
返回严格等于 searchElement 的最后一个元素的索引。如果没有这样的元素,则返回 -1。从索引 fromIndex 开始搜索,接下来访问更低的索引。
> ['a', 'b', 'a'].lastIndexOf('a')
2
> ['a', 'b', 'a'].lastIndexOf('a', 1)
0
> ['a', 'b', 'a'].lastIndexOf('c')
-1.map<U>(mapFunc: (value: T, index: number, array: Array<T>) => U, thisArg?: any): U[] [R, ES5]
返回一个新数组,其中每个元素都是将 mapFunc 应用于接收器的相应元素的结果。
> [1, 2, 3].map(x => x * 2)
[ 2, 4, 6 ]
> ['a', 'b', 'c'].map((x, i) => i)
[ 0, 1, 2 ].pop(): T | undefined [W, ES3]
删除并返回接收器的最后一个元素。也就是说,它将接收器的末尾视为堆栈。与 .push() 相反。
> const arr = ['a', 'b', 'c'];
> arr.pop()
'c'
> arr
[ 'a', 'b' ].push(...items: T[]): number [W, ES3]
向接收器的末尾添加零个或多个 items。也就是说,它将接收器的末尾视为堆栈。返回值是更改后接收器的长度。与 .pop() 相反。
> const arr = ['a', 'b'];
> arr.push('c', 'd')
4
> arr
[ 'a', 'b', 'c', 'd' ]我们可以通过将数组展开 (...) 到参数中来推送数组
> const arr = ['x'];
> arr.push(...['y', 'z'])
3
> arr
[ 'x', 'y', 'z' ] .reduce<U>(callback: (accumulator: U, element: T, index: number, array: T[]) => U, init?: U): U [R, ES5]
此方法生成接收器的摘要:它将所有数组元素提供给 callback,后者将当前摘要(在参数 accumulator 中)与当前数组元素组合并返回下一个 accumulator
const accumulator_0 = callback(init, arr[0]);
const accumulator_1 = callback(accumulator_0, arr[1]);
const accumulator_2 = callback(accumulator_1, arr[2]);
// Etc..reduce() 的结果是 callback 访问所有数组元素后的最后一个结果。
> [1, 2, 3].reduce((accu, x) => accu + x, 0)
6
> [1, 2, 3].reduce((accu, x) => accu + String(x), '')
'123'如果未提供 init,则使用索引 0 处的数组元素,并首先访问索引 1 处的元素。因此,数组的长度必须至少为 1。
.reduceRight<U>(callback: (accumulator: U, element: T, index: number, array: T[]) => U, init?: U): U [R, ES5]
与 .reduce() 类似,但以相反的顺序访问数组元素,从最后一个元素开始。
> [1, 2, 3].reduceRight((accu, x) => accu + String(x), '')
'321'.reverse(): this [W, ES1]
重新排列接收器的元素,使其顺序相反,然后返回接收器。
> const arr = ['a', 'b', 'c'];
> arr.reverse()
[ 'c', 'b', 'a' ]
> arr
[ 'c', 'b', 'a' ].shift(): T | undefined [W, ES3]
移除并返回接收器的第一个元素。与 .unshift() 相反。
> const arr = ['a', 'b', 'c'];
> arr.shift()
'a'
> arr
[ 'b', 'c' ].slice(start=0, end=this.length): T[] [R, ES3]
返回一个新数组,其中包含接收器中索引介于(包括)start 和(不包括)end 之间的元素。
> ['a', 'b', 'c', 'd'].slice(1, 3)
[ 'b', 'c' ]
> ['a', 'b'].slice() // shallow copy
[ 'a', 'b' ]允许负索引并将其添加到 .length
> ['a', 'b', 'c'].slice(-2)
[ 'b', 'c' ].some(callback: (value: T, index: number, array: Array<T>) => boolean, thisArg?: any): boolean [R, ES5]
如果 callback 至少为一个元素返回真值,则返回 true。否则,它返回 false。一旦接收到真值,它就会停止。此方法对应于数学中的存在量词(“存在”,∃)。
> [1, 2, 3].some(x => x < 0)
false
> [1, -2, 3].some(x => x < 0)
true相关方法:.every()(“对所有”)。
.sort(compareFunc?: (a: T, b: T) => number): this [W, ES1]
对接收器进行排序并返回它。默认情况下,它对元素的字符串表示形式进行排序。它按字典顺序和字符的代码单元值(字符代码)进行排序
> ['pie', 'cookie', 'éclair', 'Pie', 'Cookie', 'Éclair'].sort()
[ 'Cookie', 'Pie', 'cookie', 'pie', 'Éclair', 'éclair' ]
> [200, 3, 10].sort()
[ 10, 200, 3 ]我们可以通过 compareFunc 自定义排序顺序,它返回一个数字,该数字是
a < b,则为负数。a === b,则为零。a > b,则为正数。对数字进行排序的技巧(存在数字溢出或下溢的风险)
> [200, 3, 10].sort((a, b) => a - b)
[ 3, 10, 200 ]
.sort() 是稳定的
从 ECMAScript 2019 开始,保证排序是稳定的:如果排序认为元素相等,则排序不会更改这些元素的顺序(相对于彼此)。
.splice(start: number, deleteCount=this.length-start, ...items: T[]): T[] [W, ES3]
在索引 start 处,它删除 deleteCount 个元素并插入 items。它返回已删除的元素。
> const arr = ['a', 'b', 'c', 'd'];
> arr.splice(1, 2, 'x', 'y')
[ 'b', 'c' ]
> arr
[ 'a', 'x', 'y', 'd' ]start 可以为负数,如果为负数,则将其添加到 .length
> ['a', 'b', 'c'].splice(-2, 2)
[ 'b', 'c' ].toString(): string [R, ES1]
通过 String() 将所有元素转换为字符串,用逗号分隔并连接它们,然后返回结果。
> [1, 2, 3].toString()
'1,2,3'
> ['1', '2', '3'].toString()
'1,2,3'
> [].toString()
''.unshift(...items: T[]): number [W, ES3]
将 items 插入到接收器的开头,并在修改后返回其长度。
> const arr = ['c', 'd'];
> arr.unshift('e', 'f')
4
> arr
[ 'e', 'f', 'c', 'd' ].values(): Iterable<T> [R, ES6]
返回接收器值的迭代器。
> Array.from(['a', 'b'].values())
[ 'a', 'b' ] 测验
参见测验应用程序。