ECMAScript5中的数组方法

This is the programmer's leisure time series!

ECMAScript5中的数组方法

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


一. forEach()

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

 var data = [1,2,3,4,5];                             // 要求和的数组  
 // 计算数组元素的和值  
 var sum = 0;                                        // 初始为0  
 data.forEach(function(value){ sum += value; }); // 将每个值累加到sum上  
 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异常,循环会提前终止:

 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");

二. map()

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

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

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

三. filter()

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

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

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

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

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

 a = a.filter(function(x){ return x !== undefined && x != null; });

四. every()和some()

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

 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:

 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。

五. reduce()和reduceRight()

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

 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()一样,不同的是它按照数组索引从高到低(从右到左)处理数组,而不是从低到高。如果化简操作的优先顺序是从右到左,你可能想使用它,例如:

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

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

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

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

 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}

六. indexOf()和lastIndexOf()

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

 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()的第二个参数来查找除了第一个以外匹配的值。

 // 在数组中查找所有出现的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()方法,它们和数组方法的功能类似。

本博文摘录自:
《JavaScript权威指南(原书第6版)》— 弗兰纳根


_END._🍻

Show Comments

Get the latest posts delivered right to your inbox.