ECMAScript5中的数组方法

概述

        ECMAScript5定义了9个心得数组方法来遍历、映射、过滤、检测、简化和搜索数组。大多数方法的第一个参数接收一个函数,并且对数组的每个元素(或一些元素)调用一次该函数。如果是稀疏数组,对不存在的元素不调用传递的函数。在大多数情况下,调用提供的函数使用三个参数:数组元素、元素的索引和数组本身。通常,只需要第一个参数值,客户忽略后两个参数。大多数ECMAScript5数组方法的第一个参数是一个函数,第二个参数是可以选的。如果有第三个参数,则调用的函数被看做是第二个参数的方法。也就是说,在调用函数时传递进去的第二个参数作为它的this关键字的值来使用。被调用的函数的返回值非常重要,但是不同的方法处理返回值的方式也不一样。ECMAScript5中的数组方法都不会修改他们调用的原是数组。当然,传递给这些方法的函数是可以修改这些数组的。

1. forEach()

        forEach() 方法从头至尾遍历数组,为每个元素调用指定的函数。传递的函数作为 forEach() 的第一个参数。然后 forEach() 使用三个参数调用该函数:数组元素、元素的索引和元素本身。如果只关心数组元素的值,可以编写只有一个参数的函数——额外的参数将忽略:

1
2
3
4
5
6
7
8
9
10
11
12
var data = [1, 2, 3, 4, 5]; // 要求和的数组
// 计算数组元素的和值
var sum = 0; // 初始值为0
data.forEach(function(value) { // 将每个值累加到sum上
sum += value;
});
sum; // 15
// 每个数组元素的值自加1
data.forEach(function(v, i, a) {
a[i] = v + 1;
});
data; // => [2,3,4,5,6]

        forEach() 方法无法再所有元素都传递给调用的函数之前终止遍历。也就是说,没有像for循环中使用的响应的break语句。如果要提前终止,必须把 forEach() 方法放在一个try块中,并能抛出一个异常。如果 forEach() 方法调用的函数抛出 forEach.break 异常,循环会提前终止:

1
2
3
4
5
6
7
8
9
function foreach(a, f, t) {
try {
a.forEach(f, t);
} catch (e) {
if (e === foreach.break) return;
else throw e;
}
}
foreach.break = new Error("StopIteration");
2. map()

        map() 方法将调用的数组的每个元素传递给指定的函数,并返回一个数组,它包含该函数的返回值。例如:

1
2
3
4
a = [1, 2, 3];
b = a.map(function(x) {
return x * x;
}); //b是[1, 4, 9]

        传递给 map() 的函数的调用方式和传递给 forEach() 的函数的调用方式一样。但传递给 map() 的函数应该有返回值。注意, map() 返回的是新数组:它不修改调用的数组。如果是稀疏数组,返回的也是相同方式的稀疏数组:它具有相同的长度,相同的缺失元素。

3. filter()

        filter() 方法返回的数组元素是调用的数组的一个子集。传递的函数是用来逻辑判定的:该函数返回 truefalse 。调用判定函数就像调用 forEach()map() 一样。如果返回值为 true 或者 false ,那么传递给判定函数的就是这个子集的成员,它将被添加到一个座位返回值的数组中。例如:

1
2
3
4
5
6
7
a = [5, 4, 3, 2, 1];
smallvalue = a.filter(function(x) {
return x < 3;
}); // [2 ,1]
everyoher = a.filter(function(x) {
return i % 2 == 0;
}); // [5, 3, 1]

        filter() 会跳过稀疏数组中缺少的元素,它的返回数组总是稠密的。为了压缩稀疏数组的空缺,代码如下:

1
var dense = sparse.filter(function () { return true; });

        甚至,压缩空缺并删除 undefinednull 元素,可以这样使用 filter()

1
a = a.filter(function (x) { return x != undefined && x != null; });
4. every()和some()

        every()some() 方法是数组的逻辑判定:它们对数组元素应用指定的函数进行判定,返回 truefalse
every() 方法就像数学中的”针对所有”的量词∀:当且仅当数组中的所有元素调用判定函数都返回true,它才返回true:

1
2
3
4
5
6
7
a = [1, 2, 3, 4, 5];
a.every(function(x) {
return x < 10;
}); // => true:所有的值<10
a.every(function(x) {
return x % 2 === 0;
}); // => false: 不是所有的值都是偶数

        some() 方法就像数学的”存在”量词∃:当数组中至少有一个元素调用判定函数返回true,它就返回true;并且当且仅当数值中的所有元素调用判定函数都返回false,它才返回false:

1
2
3
4
5
a = [1, 2, 3, 4, 5];
a.some(function(x) {
return x % 2 === 0;
}); // => true:a包含有偶数
a.some(isNaN); // => false: a不包含非数值元素

        一旦 every()some() 确认该返回什么值他们就会停止遍历数组元素。 some() 在判定函数第一次返回true后就返回true,但如果判定函数一直返回false,它将会遍历整个数组。 every() 切好相反:他在判定函数第一次返回false就返回false,但如果判定函数一直返回true,它将会便利整个数组。注意,根据数学上的惯例,在空数组上调用时, every() 返回true, some() 返回false。

5. reduce()和reduceRight()

        reduce()reduceRight() 方法使用指定的函数将数组元素进行组合,生成单个值。这在函数式编程中是常见的操作们也可以称为”注入”和”折叠”:

1
2
3
4
5
6
7
8
9
10
var a = [1, 2, 3, 4, 5];
var sum = a.reduce(function(x, y) {
return x + y
}, 0); // => 数组求和
var product = a.reduce(function(x, y) {
return x * y
}, 1); // => 数组求积
var max = a.reduce(function(x, y) {
return (x > y) ? x : y;
}); // => 求最大值

        reduce() 需要两个参数。第一个是执行化简操作的函数。化简函数的任务就是用某种方法把两个值组合或花间为一个值,并返回化简后的值。在上述例子中,函数通过加法、乘法或取最大值的方法组合两个值。第二个(可选)的参数是一个传递给函数的初始值。
        reduce() 使用的函数与 forEach()map() 使用的函数不同。比较熟悉的是,数组元素、元素的索引和数组本身将作为第2~4个参数传递给函数。第一个参数是到目前为止的化简操作累积的结果。第一次调用函数式,第一个参数是一个初始值,它就是传递给 reduce() 的第二个参数。在接下来的调用中,这个值就是上一次化简函数的返回值。在示例的第一个例子中,第一次调用化简函数时的参数是0和1。将两者相加并返回1。再次调用时的参数是1和2,它返回3。然后它计算3+3=6、6+4=10,最后计算10+5=15。最后的值是15, reduce() 返回这个值。
        上面第三次调用 reduce() 时只有一个参数:没有指定初始值。当不指定初始值调用 reduce() 时,它将使用数组的第一个元素作为其初始值。这意味着第一次调用化简函数就使用了第一个和第二个数组元素作为其第一个和第二个参数。在上面求和和求积的例子里面,可以省略初始值参数。
在空数组上,不带初始值参数调用 reduce() 将导致类型错误异常。如果调用它的时候只有一个值——数组只有一个元素并且没有指定初始值,或者有一个空数组并指定一个初始值—— reduce() 只是简单地返回那个值而不会调用化简函数。
        reduceRight() 的工作原理和 reduce() 一样,不用的是它按照数组索引从高倒地(从右到左)处理数组,而不是从低到高。如果化简操作的优先顺序是从右到左,我们可以把它用在这些地方:

1
2
3
4
5
var a = [2, 3, 4];
// 计算2^(3^4)。乘方操作的优先顺序是从右到左
var big = a.reduceRight(function(accmulator, value) {
return Math.pow(value, accmulator);
});

        reduce()reduceRight() 都能接收一个可选参数,它指定了化简函数调用时的 this关键字 的值。可选的初始值参数仍然需要占一个位置。如果想让化简函数作为一个特殊对象的方法调用,可以想到 Function.bind() 方法.
        上面说的 every()some() 方法是一种类型的数组化操作。但是不同的是,它们会尽早终止遍历而不总是访问每一个数组元素。
        数学计算不是 reduce()reduceRight() 的唯一用途。比如,我们可以用它写一个 union() 函数:它计算两个对象的”并集”,并返回另一个新对象,新对象具有二者的属性。该函数期待两个对象并返回另一个对象,所以它的工作原理和一个化简函数一样,并且可以使用 reduce() 来把它一般化,计算任意数目的对象的”并集”。

1
2
3
4
5
6
7
8
var objects = [{
x: 1
}, {
y: 2
}, {
z: 3
}];
var merged = objects.reduce(union); // => {x:1,y:2,z:3}

        当两个对象拥有同名的属性时, union() 函数使用第一个参数的属性值。这样, reduce()reduceRight() 在使用 union() 时会给出不同的结果:

1
2
3
4
5
6
7
8
9
10
11
12
var objects = [{
x: 1,
a: 1
}, {
y: 2,
a: 2
}, {
z: 3,
a: 3
}];
var leftunion = objects.reduce(union); // => {x:1, y:2, z:3, a:1}
var rightunion = objects.reduceRight(union); // => {x:1, y:2, z:3, a:3}
6.indexOf()和lastIndexOf()

        indexOf()lastIndexOf() 搜索整个数组中具有给定值的元素,返回找到的第一个元素的索引或者如果没有找到就返回-1。 indexOf() 从头至尾搜索,而 lastIndexOf() 则反向搜索。

1
2
3
4
var a = [0, 1, 2, 1, 0];
a.indexOf(1); // => 1:a[1]是1
a.lastIndexOf(1); // => 3:a[3]是1
a.indexOf(3); // => -1:没有值为3的元素

        不同于其他方法, indexOf()lastIndexOf() 方法不接受一个函数作为其参数。第一个参数是需要搜索的值,第二个参数是可选的:它指定数组中的一个索引,从那里开始搜索,如果省略该参数, indexOf() 从头开始搜索,而 lastIndexOf() 从末尾开始搜索。第二个参数也可以是负数,它代表对数组末尾的偏移量,对于 splice() 方法:例如,-1指定数组的最后一个元素。
如下函数在一个数组中搜索指定的值并返回包含所有匹配的数组索引的一个数组。它展示了如何运用 indexOf() 的第二个参数来查找除了第一个意外匹配的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 在数组中查找所有出现的x,并返回一个包含匹配索引的数组
function findall(a, x) {
var results = [], // 将会返回的数组
len = a.length, // 待搜索数组的长度
pos = 0; // 开始搜索的位置
while (pos < len) {
// 循环搜索多个元素...
pos = a.indexOf(x, pos); // 搜索
if (pos === -1) { // 未找到,就完成搜索
break;
}
results.push(pos); // 否则,在数组中存储索引
pos = pos + 1; // 并从下一个位置开始搜索
}
return results; // 返回包含索引的数组
}

        字符串也有 indexOf()lastIndexOf() 方法,它们和数组方法的功能类似。

上次更新 2020-06-03