数组是从索引(自然数,从零开始)到任意值的映射。值(映射的范围)称为数组的元素。创建数组最方便的方法是通过数组字面量。这样的字面量枚举数组元素;元素的位置隐式地指定其索引。
在本章中,我将首先介绍 基本的数组机制,例如索引访问和 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()
时预先分配连续内存,这可能会稍微提高性能。但是,请确保增加的冗长度和冗余是值得的!
// 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
=
[
[
'.'
,
'.'
,
'.'
],
[
'.'
,
'.'
,
'.'
],
[
'.'
,
'.'
,
'.'
]
];
使用数组索引时, 必须牢记以下限制:
超出范围的索引将被视为普通属性键(字符串!)。它们不会显示为数组元素,也不会影响属性 length
。例如:
> var arr = []; > arr[-1] = 'a'; > arr [] > arr['-1'] 'a' > arr[4294967296] = 'b'; > arr [] > arr['4294967296'] 'b'
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))
等于 P
且 ToUint32(P)
不等于 232−1。稍后将解释这意味着什么。换句话说,在规范的世界中,括号中的所有值都将转换为字符串并解释为属性键,甚至是数字。以下交互演示了这一点:
> var arr = ['a', 'b']; > arr['0'] 'a' > arr[0] 'a'
要成为数组索引,属性键 P
(字符串!)必须等于以下计算的结果:
P
转换为数字。这意味着数组索引必须是 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
属性的基本功能是 跟踪数组中的最高索引:
> [ '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()
会将空洞、undefined
和 null
转换为 空字符串:
> ['a',, 'b'].join('-') 'a--b' > [ 'a', undefined, 'b' ].join('-') 'a--b'
sort()
在排序时会保留空洞
> ['a',, 'b'].sort() // length of result is 3 [ 'a', 'b', , ]
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.isArray(obj)
obj
是数组,则返回 true
。 它可以正确处理跨 域(窗口或框架)的对象,这与 instanceof
不同(请参阅 陷阱:跨域(框架或窗口))。Array.prototype.shift()
> 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?, ...)
> 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
)
此函数比较 a
和 b
并返回
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
作为比较函数,arr
按 name
排序
> 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()
会将 undefined
和 null
转换为空字符串
> [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?)
本例检查数组中的每个数字是否都是偶数
> 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?)
本例检查数组中是否存在偶数
> 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
。然后 previousValue
是 initialValue
,而 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
被称为 fold
或 foldl
(左折叠),而 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'
JavaScript 中的某些对象 看起来像数组,但它们不是数组。这通常意味着它们具有索引访问和 length
属性,但没有数组方法。示例包括特殊变量 arguments
、DOM 节点列表和字符串。类数组对象和泛型方法 提供了处理类数组对象的技巧。