第 18 章 数组
目录
购买此书
(广告,请不要屏蔽。)

第 18 章 数组

数组是从索引(自然数,从零开始)到任意值的映射。值(映射的范围)称为数组的元素。创建数组最方便的方法是通过数组字面量。这样的字面量枚举数组元素;元素的位置隐式地指定其索引。

在本章中,我将首先介绍 基本的数组机制,例如索引访问和 length 属性,然后介绍数组方法。

概述

本节简要概述了数组。详细信息 将在后面解释。

作为第一个示例,我们通过数组字面量(参见 创建数组)创建一个数组 arr 并访问元素(参见 数组索引):

> var arr = [ 'a', 'b', 'c' ]; // array literal
> arr[0]  // get element 0
'a'
> arr[0] = 'x';  // set element 0
> arr
[ 'x', 'b', 'c' ]

我们可以使用数组属性 length(参见 length)来 删除和追加元素:

> var arr = [ 'a', 'b', 'c' ];
> arr.length
3
> arr.length = 2;  // remove an element
> arr
[ 'a', 'b' ]
> arr[arr.length] = 'd';  // append an element
> arr
[ 'a', 'b', 'd' ]

数组方法 push() 提供了另一种追加元素的方法:

> var arr = [ 'a', 'b' ];
> arr.push('d')
3
> arr
[ 'a', 'b', 'd' ]

数组是映射,而不是元组

ECMAScript 标准将数组 指定为从索引到值的映射(字典)。 换句话说,数组可能不是连续的,并且其中可能存在空洞。例如:

> var arr = [];
> arr[0] = 'a';
'a'
> arr[2] = 'b';
'b'
> arr
[ 'a', , 'b' ]

前面的数组有一个空洞:索引 1 处没有元素。数组中的空洞 更详细地解释了空洞。

请注意,大多数 JavaScript 引擎在内部优化没有空洞的数组,并将它们连续存储。

数组也可以具有属性

数组 仍然是对象,并且可以具有对象属性。这些属性不被视为实际数组的一部分;也就是说,它们不被视为数组元素:

> var arr = [ 'a', 'b' ];
> arr.foo = 123;
> arr
[ 'a', 'b' ]
> arr.foo
123

创建数组

您可以通过数组字面量创建数组:

var myArray = [ 'a', 'b', 'c' ];

数组中的尾随逗号将被忽略:

> [ 'a', 'b' ].length
2
> [ 'a', 'b', ].length
2
> [ 'a', 'b', ,].length  // hole + trailing comma
3

数组构造函数

两种使用构造函数 Array 的方法:您可以创建一个具有给定长度的空数组,或者创建一个元素为给定值的数组。对于此构造函数,new 是可选的:将其作为普通函数调用(不带 new)与将其作为构造函数调用相同。

创建具有给定长度的空数组

具有给定长度的空 数组中只有空洞!因此,使用此版本的构造函数很少有意义:

> var arr = new Array(2);
> arr.length
2
> arr  // two holes plus trailing comma (ignored!)
[ , ,]

某些引擎可能会在您以这种方式调用 Array() 时预先分配连续内存,这可能会稍微提高性能。但是,请确保增加的冗长度和冗余是值得的!

用元素初始化数组(避免!)

这种 调用 Array 的方式类似于数组字面量:

// The same as ['a', 'b', 'c']:
var arr1 = new Array('a', 'b', 'c');

问题是您不能创建只有一个数字的数组,因为这会被解释为创建一个 length 为该数字的数组:

> new Array(2)  // alas, not [ 2 ]
[ , ,]

> new Array(5.7)  // alas, not [ 5.7 ]
RangeError: Invalid array length

> new Array('abc')  // ok
[ 'abc' ]

多维数组

如果元素需要多个维度,则必须嵌套数组。 创建此类嵌套数组时,最里面的数组可以根据需要增长。但是,如果要直接访问元素,则至少需要创建外部数组。在以下示例中,我创建了一个用于井字游戏的 3x3 矩阵。该矩阵完全填充了数据(而不是让行根据需要增长):

// Create the Tic-tac-toe board
var rows = [];
for (var rowCount=0; rowCount < 3; rowCount++) {
    rows[rowCount] = [];
    for (var colCount=0; colCount < 3; colCount++) {
        rows[rowCount][colCount] = '.';
    }
}

// Set an X in the upper right corner
rows[0][2] = 'X';  // [row][column]

// Print the board
rows.forEach(function (row) {
    console.log(row.join(' '));
});

以下是输出:

. . X
. . .
. . .

我希望该示例演示一般情况。显然,如果矩阵很小且维度固定,则可以通过数组字面量设置它:

var rows = [ ['.','.','.'], ['.','.','.'], ['.','.','.'] ];

数组索引

使用数组索引时, 必须牢记以下限制:

  • 索引是范围为 0 ≤ i < 232−1 的数字 i
  • 最大长度为 232−1。

超出范围的索引将被视为普通属性键(字符串!)。它们不会显示为数组元素,也不会影响属性 length。例如:

> var arr = [];

> arr[-1] = 'a';
> arr
[]
> arr['-1']
'a'

> arr[4294967296] = 'b';
> arr
[]
> arr['4294967296']
'b'

in 运算符和索引

in 运算符 检测对象是否具有给定键的属性。但它也可以用来确定数组中是否存在给定的元素索引。例如:

> var arr = [ 'a', , 'b' ];
> 0 in arr
true
> 1 in arr
false
> 10 in arr
false

删除数组元素

除了删除属性外,delete 运算符 还会删除数组元素。删除元素会创建空洞length 属性不会更新):

> var arr = [ 'a', 'b' ];
> arr.length
2
> delete arr[1]  // does not update length
true
> arr
[ 'a',  ]
> arr.length
2

您还可以通过减少数组的长度来删除尾随数组元素(有关详细信息,请参阅 length)。要删除元素而不创建空洞(即,后续元素的索引递减),请使用 Array.prototype.splice()(请参阅 添加和删除元素(破坏性))。在本例中,我们删除了索引 1 处的两个元素:

> var arr = ['a', 'b', 'c', 'd'];
> arr.splice(1, 2) // returns what has been removed
[ 'b', 'c' ]
> arr
[ 'a', 'd' ]

数组索引详解

提示

这是一个高级部分。通常,您不需要了解此处解释的详细信息。

数组索引并非表面上那样。 到目前为止,我一直假装数组 索引是数字。这就是 JavaScript 引擎在内部实现数组的方式。但是,ECMAScript 规范对索引的看法不同。改述 第 15.4 节

  • 属性键 P(字符串)是数组索引,当且仅当 ToString(ToUint32(P)) 等于 PToUint32(P) 不等于 232−1。稍后将解释这意味着什么。
  • 键为数组索引的数组属性称为元素

换句话说,在规范的世界中,括号中的所有值都将转换为字符串并解释为属性键,甚至是数字。以下交互演示了这一点:

> var arr = ['a', 'b'];
> arr['0']
'a'
> arr[0]
'a'

要成为数组索引,属性键 P(字符串!)必须等于以下计算的结果:

  1. P 转换为数字。
  2. 将数字转换为 32 位无符号整数。
  3. 将整数转换为字符串。

这意味着数组索引必须是 32 位范围内 0 ≤ i < 232−1 的字符串化整数 i。规范中明确排除了上限(如前所述)。它保留用于最大长度。要查看此定义的工作原理,让我们使用 通过按位运算符实现 32 位整数 中的函数 ToUint32()

首先,不包含数字的字符串始终转换为 0,字符串化后,该字符串不等于该字符串:

> ToUint32('xyz')
0
> ToUint32('?@#!')
0

其次,超出范围的字符串化整数也会转换为完全不同的整数,字符串化后该整数不等于该字符串:

> ToUint32('-1')
4294967295
> Math.pow(2, 32)
4294967296
> ToUint32('4294967296')
0

第三,字符串化的非整数数字将转换为整数,这些整数又是不同的:

> ToUint32('1.371')
1

请注意,规范还强制数组索引没有指数:

> ToUint32('1e3')
1000

并且它们没有前导零:

> var arr = ['a', 'b'];
> arr['0']  // array index
'a'
> arr['00'] // normal property
undefined

length

length 属性的基本功能是 跟踪数组中的最高索引:

> [ 'a', 'b' ].length
2
> [ 'a', , 'b' ].length
3

因此,length 不会计算元素的数量,因此您必须编写自己的函数来执行此操作。例如:

function countElements(arr) {
    var elemCount = 0;
    arr.forEach(function () {
        elemCount++;
    });
    return elemCount;
}

为了计算元素(非空洞),我们利用了 forEach 跳过空洞的事实。以下是交互:

> countElements([ 'a', 'b' ])
2
> countElements([ 'a', , 'b' ])
2

手动增加数组的长度

手动增加数组的长度 对数组的影响非常小;它只会创建空洞:

> var arr = [ 'a', 'b' ];
> arr.length = 3;
> arr  // one hole at the end
[ 'a', 'b', ,]

最后一个结果末尾有两个逗号,因为尾随逗号是可选的,因此始终会被忽略。

我们刚才所做的并没有添加任何元素:

> countElements(arr)
2

但是,length 属性确实充当指针,指示在何处插入新元素。例如:

> arr.push('c')
4
> arr
[ 'a', 'b', , 'c' ]

因此,通过 Array 构造函数设置数组的初始长度会创建一个完全空的数组:

> var arr = new Array(2);
> arr.length
2
> countElements(arr)
0

减少数组的长度

如果减少数组的 长度,则新长度及以上的所有元素都将被删除:

> var arr = [ 'a', 'b', 'c' ];
> 1 in arr
true
> arr[1]
'b'

> arr.length = 1;
> arr
[ 'a' ]
> 1 in arr
false
> arr[1]
undefined

清空数组

如果将数组的长度设置为 0,则它将变为空。 这允许您为其他人清空数组。例如:

function clearArray(arr) {
    arr.length = 0;
}

以下是交互:

> var arr = [ 'a', 'b', 'c' ];
> clearArray(arr)
> arr
[]

但是请注意,这种方法可能很慢,因为每个数组元素都被显式删除。具有讽刺意味的是,创建一个新的空数组通常更快:

arr = [];

清空共享数组

您需要注意 将数组的长度设置为零会影响共享该数组的所有人:

> var a1 = [1, 2, 3];
> var a2 = a1;
> a1.length = 0;

> a1
[]
> a2
[]

相反,分配一个空数组则不会:

> var a1 = [1, 2, 3];
> var a2 = a1;
> a1 = [];

> a1
[]
> a2
[ 1, 2, 3 ]

最大长度

最大数组 长度为 232−1:

> var arr1 = new Array(Math.pow(2, 32));  // not ok
RangeError: Invalid array length

> var arr2 = new Array(Math.pow(2, 32)-1);  // ok
> arr2.push('x');
RangeError: Invalid array length

数组中的空洞

数组是从索引到值的映射。这意味着数组可以有空洞,即数组中缺少小于长度的索引。 读取这些索引之一处的元素将返回 undefined

提示

建议您避免数组中的空洞。JavaScript 处理它们的方式不一致(即,某些方法忽略它们,而其他方法则不忽略)。值得庆幸的是,您通常不需要知道如何处理空洞:它们很少有用,并且会对性能产生负面影响。

创建空洞

您可以通过 赋值给数组索引来创建空洞:

> var arr = [];
> arr[0] = 'a';
> arr[2] = 'c';
> 1 in arr  // hole at index 1
false

您也可以通过在数组字面量中省略值来创建空洞:

> var arr = ['a',,'c'];
> 1 in arr  // hole at index 1
false

警告

您需要两个尾随逗号才能创建尾随空洞,因为最后一个逗号始终会被忽略:

> [ 'a', ].length
1
> [ 'a', ,].length
2

稀疏数组与密集数组

本节将研究空洞和undefined 作为元素之间的区别。鉴于读取空洞会返回 undefined,因此两者非常相似。

具有空洞的数组称为稀疏数组 没有空洞的数组称为密集数组。密集数组是连续的,并且在每个索引处都有一个元素,从零开始,到 length − 1 结束。让我们比较以下两个数组,一个稀疏数组和一个密集数组。两者非常相似:

var sparse = [ , , 'c' ];
var dense  = [ undefined, undefined, 'c' ];

空洞几乎就像在同一索引处具有元素 undefined。两个数组的长度相同:

> sparse.length
3
> dense.length
3

但是稀疏数组在索引 0 处没有元素:

> 0 in sparse
false
> 0 in dense
true

通过 for 进行迭代 对于两个数组都是相同的:

> for (var i=0; i<sparse.length; i++) console.log(sparse[i]);
undefined
undefined
c
> for (var i=0; i<dense.length; i++) console.log(dense[i]);
undefined
undefined
c

通过 forEach 进行迭代会跳过 空洞,但不会跳过 undefined 元素:

> sparse.forEach(function (x) { console.log(x) });
c
> dense.forEach(function (x) { console.log(x) });
undefined
undefined
c

哪些操作会忽略空洞,哪些操作会考虑空洞?

一些涉及 数组的操作会忽略空洞,而其他操作则会考虑空洞。本节将解释详细信息。

数组迭代方法

forEach() 会跳过 空洞:

> ['a',, 'b'].forEach(function (x,i) { console.log(i+'.'+x) })
0.a
2.b

every() 会跳过空洞(some() 也是如此):

> ['a',, 'b'].every(function (x) { return typeof x === 'string' })
true

map() 会跳过空洞,但会保留空洞:

> ['a',, 'b'].map(function (x,i) { return i+'.'+x })
[ '0.a', , '2.b' ]

filter() 可以消除 空洞:

> ['a',, 'b'].filter(function (x) { return true })
[ 'a', 'b' ]

其他数组方法

join() 会将空洞、undefinednull 转换为 空字符串:

> ['a',, 'b'].join('-')
'a--b'
> [ 'a', undefined, 'b' ].join('-')
'a--b'

sort() 在排序时会保留空洞

> ['a',, 'b'].sort()  // length of result is 3
[ 'a', 'b', ,  ]

for-in 循环

for-in 循环会正确列出 属性键(它是数组索引的超集):

> for (var key in ['a',, 'b']) { console.log(key) }
0
2

Function.prototype.apply()

apply() 会将每个空洞转换为值为 undefined 的参数。以下交互演示了这一点:函数 f() 返回其参数组成的数组。当我们传递一个包含三个空洞的数组给 apply() 以调用 f() 时,后者会收到三个 undefined 参数

> function f() { return [].slice.call(arguments) }
> f.apply(null, [ , , ,])
[ undefined, undefined, undefined ]

这意味着我们可以使用 apply() 创建一个包含 undefined 的数组:

> Array.apply(null, Array(3))
[ undefined, undefined, undefined ]

警告

apply() 会将空数组中的空洞转换为 undefined,但它不能用于填充任意数组(可能包含或不包含空洞)中的空洞。以任意数组 [2] 为例

> Array.apply(null, [2])
[ , ,]

该数组不包含任何空洞,因此 apply() 应该返回相同的数组。相反,它返回一个长度为 2 的空数组(它只包含两个空洞)。这是因为 Array() 将单个数字解释为数组长度,而不是数组元素。

从数组中移除空洞

正如我们所见,filter() 可以移除 空洞:

> ['a',, 'b'].filter(function (x) { return true })
[ 'a', 'b' ]

使用自定义函数将任意数组中的空洞转换为 undefined

function convertHolesToUndefineds(arr) {
    var result = [];
    for (var i=0; i < arr.length; i++) {
        result[i] = arr[i];
    }
    return result;
}

使用该函数

> convertHolesToUndefineds(['a',, 'b'])
[ 'a', undefined, 'b' ]

数组构造函数方法

添加和移除元素(破坏性)

节中的所有方法都是破坏性的:

Array.prototype.shift()

移除 索引 0 处的元素并返回它。后续元素的索引减 1:

> var arr = [ 'a', 'b' ];
> arr.shift()
'a'
> arr
[ 'b' ]
Array.prototype.unshift(elem1?, elem2?, ...)

给定元素添加到数组的开头。它返回新的长度:

> var arr = [ 'c', 'd' ];
> arr.unshift('a', 'b')
4
> arr
[ 'a', 'b', 'c', 'd' ]
Array.prototype.pop()

移除 数组的最后一个元素并返回它:

> var arr = [ 'a', 'b' ];
> arr.pop()
'b'
> arr
[ 'a' ]
Array.prototype.push(elem1?, elem2?, ...)

将给定元素 添加到数组的末尾。它返回新的长度:

> var arr = [ 'a', 'b' ];
> arr.push('c', 'd')
4
> arr
[ 'a', 'b', 'c', 'd' ]

apply()(请参阅 Function.prototype.apply(thisValue, argArray))允许 您将数组 arr2 破坏性地附加到另一个数组 arr1

> var arr1 = [ 'a', 'b' ];
> var arr2 = [ 'c', 'd' ];

> Array.prototype.push.apply(arr1, arr2)
4
> arr1
[ 'a', 'b', 'c', 'd' ]
Array.prototype.splice(start, deleteCount?, elem1?, elem2?, ...)

start 开始,移除 deleteCount 个元素并插入给定的元素。换句话说,您将使用 elem1elem2 等替换位置 start 处的 deleteCount 个元素。该方法返回已移除的元素:

> var arr = [ 'a', 'b', 'c', 'd' ];
> arr.splice(1, 2, 'X');
[ 'b', 'c' ]
> arr
[ 'a', 'X', 'd' ]

特殊参数值

  • start 可以是负数,在这种情况下,它会添加到长度中以确定起始索引。因此,-1 指的是最后一个元素,依此类推。
  • deleteCount 是可选的。如果省略它(以及所有后续参数),则移除索引 start 处及其后的所有元素。

在本例中,我们移除了倒数第二个索引处及其后的所有元素

> var arr = [ 'a', 'b', 'c', 'd' ];
> arr.splice(-2)
[ 'c', 'd' ]
> arr
[ 'a', 'b' ]

排序和反转元素(破坏性)

这些方法也是破坏性的

Array.prototype.reverse()

反转 数组中元素的顺序并返回对原始(已修改)数组的引用:

> var arr = [ 'a', 'b', 'c' ];
> arr.reverse()
[ 'c', 'b', 'a' ]
> arr // reversing happened in place
[ 'c', 'b', 'a' ]
Array.prototype.sort(compareFunction?)

数组进行排序并返回它:

> var arr = ['banana', 'apple', 'pear', 'orange'];
> arr.sort()
[ 'apple', 'banana', 'orange', 'pear' ]
> arr  // sorting happened in place
[ 'apple', 'banana', 'orange', 'pear' ]

请记住,排序是通过将值转换为字符串来比较值的,这意味着数字不是按数字顺序排序的

> [-1, -20, 7, 50].sort()
[ -1, -20, 50, 7 ]

您可以通过提供可选参数 compareFunction 来解决此问题,该参数控制排序方式。它具有以下签名

function compareFunction(a, b)

此函数比较 ab 并返回

  • 如果 a 小于 b,则返回小于零的整数(例如,-1
  • 如果 a 等于 b,则返回零
  • 如果 a 大于 b,则返回大于零的整数(例如,1

比较数字

对于数字,您可以 简单地返回 a-b,但这可能会导致数字溢出。为了防止这种情况发生,您需要更详细的代码:

function compareCanonically(a, b) {
    if (a < b) {
        return -1;
    } else if (a > b) {
        return 1;
    } else {
        return 0;
    }
}

我不喜欢嵌套的条件运算符。但是 在这种情况下,代码简洁得多,我倾向于推荐它:

function compareCanonically(a, b) {
    return a < b ? -1 : (a > b ? 1 : 0);
}

使用该函数

> [-1, -20, 7, 50].sort(compareCanonically)
[ -20, -1, 7, 50 ]

比较字符串

对于字符串,您可以 使用 String.prototype.localeCompare(请参阅 比较字符串

> ['c', 'a', 'b'].sort(function (a,b) { return a.localeCompare(b) })
[ 'a', 'b', 'c' ]

比较对象

参数 compareFunction 对于排序对象也很有用

var arr = [
    { name: 'Tarzan' },
    { name: 'Cheeta' },
    { name: 'Jane' } ];

function compareNames(a,b) {
    return a.name.localeCompare(b.name);
}

使用 compareNames 作为比较函数,arrname 排序

> arr.sort(compareNames)
[ { name: 'Cheeta' },
  { name: 'Jane' },
  { name: 'Tarzan' } ]

连接、切片、拼接(非破坏性)

以下方法对数组执行各种非破坏性操作:

Array.prototype.concat(arr1?, arr2?, ...)

创建一个 新数组,其中包含接收者的所有元素,后跟数组 arr1 的所有元素,依此类推。如果其中一个参数不是数组,则将其作为元素添加到结果中(例如,此处的第一个参数 'c'):

> var arr = [ 'a', 'b' ];
> arr.concat('c', ['d', 'e'])
[ 'a', 'b', 'c', 'd', 'e' ]

调用 concat() 的数组不会更改

> arr
[ 'a', 'b' ]
Array.prototype.slice(begin?, end?)

将数组 元素复制到一个新数组中,从 begin 开始,直到但不包括 end 处的元素:

> [ 'a', 'b', 'c', 'd' ].slice(1, 3)
[ 'b', 'c' ]

如果缺少 end,则使用数组长度

> [ 'a', 'b', 'c', 'd' ].slice(1)
[ 'b', 'c', 'd' ]

如果缺少两个索引,则复制数组

> [ 'a', 'b', 'c', 'd' ].slice()
[ 'a', 'b', 'c', 'd' ]

如果任一索引为负数,则将数组长度添加到其中。因此,-1 指的是最后一个元素,依此类推

> [ 'a', 'b', 'c', 'd' ].slice(1, -1)
[ 'b', 'c' ]
> [ 'a', 'b', 'c', 'd' ].slice(-2)
[ 'c', 'd' ]
Array.prototype.join(separator?)

通过 对所有数组元素应用 toString() 并将字符串放在结果之间的 separator 中来创建一个字符串。如果省略 separator,则使用 ','

> [3, 4, 5].join('-')
'3-4-5'
> [3, 4, 5].join()
'3,4,5'
> [3, 4, 5].join('')
'345'

join() 会将 undefinednull 转换为空字符串

> [undefined, null].join('#')
'#'

数组中的空洞也会转换为空字符串

> ['a',, 'b'].join('-')
'a--b'

搜索值(非破坏性)

以下方法在 数组中搜索值:

Array.prototype.indexOf(searchValue, startIndex?)

startIndex 开始在数组中搜索 searchValue。它返回第一次出现的索引,如果未找到则返回 -1。如果 startIndex 为负数,则将数组长度添加到其中;如果缺少它,则搜索整个数组:

> [ 3, 1, 17, 1, 4 ].indexOf(1)
1
> [ 3, 1, 17, 1, 4 ].indexOf(1, 2)
3

搜索时使用严格相等(请参阅 相等运算符:=== 与 ==),这意味着 indexOf() 找不到 NaN

> [NaN].indexOf(NaN)
-1
Array.prototype.lastIndexOf(searchElement, startIndex?)

startIndex 开始向后搜索数组中的 searchElement。它返回第一次出现的索引,如果未找到则返回 -1。如果 startIndex 为负数,则将数组长度添加到其中;如果缺少它,则搜索整个数组。搜索时使用严格相等(请参阅 相等运算符:=== 与 ==

> [ 3, 1, 17, 1, 4 ].lastIndexOf(1)
3
> [ 3, 1, 17, 1, 4 ].lastIndexOf(1, -3)
1

迭代(非破坏性)

迭代 方法使用函数迭代数组。我区分了三种迭代方法,所有这些方法都是非破坏性的:检查方法 主要观察数组的内容;转换方法 从接收者派生一个新数组;归约方法 根据接收者的元素计算结果。

检查方法

本节中描述的每种方法 如下所示:

arr.examinationMethod(callback, thisValue?)

这种方法采用以下参数

  • callback 是它的第一个参数,它调用的函数。根据检查方法的不同,回调函数返回布尔值或不返回任何值。它具有以下签名

    function callback(element, index, array)

    element 是供 callback 处理的数组元素,index 是元素的索引,array 是调用 examinationMethod 的数组。

  • thisValue 允许您在 callback 内部配置 this 的值。

现在介绍我刚刚描述的 签名的检查方法:

Array.prototype.forEach(callback, thisValue?)

迭代数组的元素

var arr = [ 'apple', 'pear', 'orange' ];
arr.forEach(function (elem) {
    console.log(elem);
});
Array.prototype.every(callback, thisValue?)

如果 回调函数对每个元素都返回 true,则返回 true。一旦回调函数返回 false,它就会停止迭代。请注意,不返回值会导致隐式返回 undefinedevery() 会将其解释为 falseevery() 的工作方式类似于全称量词(“对所有”)。

本例检查数组中的每个数字是否都是偶数

> function isEven(x) { return x % 2 === 0 }
> [ 2, 4, 6 ].every(isEven)
true
> [ 2, 3, 4 ].every(isEven)
false

如果数组为空,则结果为 true(并且不调用 callback

> [].every(function () { throw new Error() })
true
Array.prototype.some(callback, thisValue?)

如果 回调函数至少对一个元素返回 true,则返回 true。一旦回调函数返回 true,它就会停止迭代。请注意,不返回值会导致隐式返回 undefinedsome 会将其解释为 falsesome() 的工作方式类似于存在量词(“存在”)。

本例检查数组中是否存在偶数

> function isEven(x) { return x % 2 === 0 }
> [ 1, 3, 5 ].some(isEven)
false
> [ 1, 2, 3 ].some(isEven)
true

如果数组为空,则结果为 false(并且不调用 callback

> [].some(function () { throw new Error() })
false

forEach() 的一个潜在缺陷是它不支持 break 或类似的东西来提前中止循环。如果您需要这样做,可以使用 some()

function breakAtEmptyString(strArr) {
    strArr.some(function (elem) {
        if (elem.length === 0) {
            return true; // break
        }
        console.log(elem);
        // implicit: return undefined (interpreted as false)
    });
}

some() 如果发生中断则返回 true,否则返回 false。这允许您根据迭代是否成功完成(使用 for 循环很难做到的事情)做出不同的反应。

转换方法

转换 方法接受一个输入数组并生成一个输出数组,而回调函数控制输出的生成方式。回调函数具有与检查相同的签名:

function callback(element, index, array)

有两种转换方法:

Array.prototype.map(callback, thisValue?)

每个输出数组元素都是将 callback 应用于输入元素的结果。例如

> [ 1, 2, 3 ].map(function (x) { return 2 * x })
[ 2, 4, 6 ]
Array.prototype.filter(callback, thisValue?)

输出数组 仅包含 callback 返回 true 的输入元素。例如:

> [ 1, 0, 3, 0 ].filter(function (x) { return x !== 0 })
[ 1, 3 ]

归约方法

对于归约,回调函数 具有不同的签名:

function callback(previousValue, currentElement, currentIndex, array)

参数 previousValue 是回调函数先前返回的值。当首次调用回调函数时,有两种可能性(描述适用于 Array.prototype.reduce();括号中提到了与 reduceRight() 的区别)

  • 已提供显式 initialValue。然后 previousValueinitialValue,而 currentElement 是第一个数组元素(reduceRight:最后一个数组元素)。
  • 未提供显式 initialValue。然后 previousValue 是第一个数组元素,而 currentElement 是第二个数组元素(reduceRight:最后一个数组元素和倒数第二个数组元素)。

有两种归约方法

Array.prototype.reduce(callback, initialValue?)

从左到右迭代,并按前面所述调用回调函数。该方法的结果是回调函数返回的最后一个值。此示例计算所有数组元素的总和:

function add(prev, cur) {
    return prev + cur;
}
console.log([10, 3, -1].reduce(add)); // 12

如果在只有一个元素的数组上调用 reduce,则返回该元素

> [7].reduce(add)
7

如果在空数组上调用 reduce,则必须指定 initialValue,否则会引发异常

> [].reduce(add)
TypeError: Reduce of empty array with no initial value
> [].reduce(add, 123)
123
Array.prototype.reduceRight(callback, initialValue?)
工作方式reduce() 相同,但从右到左迭代。

注意

在许多函数式编程语言中,reduce 被称为 foldfoldl(左折叠),而 reduceRight 被称为 foldr(右折叠)。

查看 reduce 方法的另一种方法是,它通过一系列二元运算符 op2 的应用来实现 n 元运算符 OP

OP1≤i≤n xi

(...(x1 op2 x2) op2 ...) op2 xn

这就是上一个代码示例中发生的情况:我们通过 JavaScript 的二元加法运算符为数组实现了一个 n 元求和运算符。

例如,让我们通过以下函数来检查两个迭代方向

function printArgs(prev, cur, i) {
    console.log('prev:'+prev+', cur:'+cur+', i:'+i);
    return prev + cur;
}

正如预期的那样,reduce() 从左到右迭代

> ['a', 'b', 'c'].reduce(printArgs)
prev:a, cur:b, i:1
prev:ab, cur:c, i:2
'abc'
> ['a', 'b', 'c'].reduce(printArgs, 'x')
prev:x, cur:a, i:0
prev:xa, cur:b, i:1
prev:xab, cur:c, i:2
'xabc'

reduceRight() 从右到左迭代

> ['a', 'b', 'c'].reduceRight(printArgs)
prev:c, cur:b, i:1
prev:cb, cur:a, i:0
'cba'
> ['a', 'b', 'c'].reduceRight(printArgs, 'x')
prev:x, cur:c, i:2
prev:xc, cur:b, i:1
prev:xcb, cur:a, i:0
'xcba'

最佳实践:迭代数组

迭代数组 arr,您有两个选择:

  • 简单的 for 循环(请参阅 for

    for (var i=0; i<arr.length; i++) {
        console.log(arr[i]);
    }
  • 数组迭代方法之一(请参阅 迭代(非破坏性))。例如,forEach()

    arr.forEach(function (elem) {
        console.log(elem);
    });

不要使用 for-in 循环(请参阅 for-in)来迭代数组。它迭代索引,而不是值。并且它在这样做时包括了普通属性的键,包括继承的键。

下一页:19. 正则表达式