《JavaScript 闯关记》之数组

数组是值的有序集合。每一个值叫作一个元素,而每一个元素在数组中有一个位置,以数字表示,称为索引。javascript

JavaScript 数组是无类型的,数组元素能够是任意类型,而且同一个数组中的不一样元素也可能有不一样的类型。数组的元素甚至也多是对象或其余数组。前端

JavaScript数组是动态的,根据须要它们会增加或缩减,而且在建立数组时无须声明一个固定的大小或者在数组大小变化时无须从新分配空间。java

JavaScript 数组多是稀疏的,数组元素的索引不必定要连续的,它们之间能够有空缺。每一个JavaScript数组都有一个length属性。针对非稀疏数组,该属性就是数组元素的个数。针对稀疏数组,length比全部元素的索引要大。ios

JavaScript 数组是 JavaScript 对象的特殊形式,数组索引实际上和碰巧是整数的属性名差很少。一般,数组的实现是通过优化的,用数字索引来访问数组元素通常来讲比访问常规的对象属性要快不少。git

数组继承自 Array.prototype 中的属性,它定义了一套丰富的数组操做方法。github

建立数组

可使用数组字面量和 new 关键字来建立数组。算法

使用数组字面量建立数组(推荐)

var empty = [];                 // 没有元素的数组
var primes = [2, 3, 5, 7, 11];  // 有5个数值的数组
var misc = [1.1, true, "a"];    // 3个不一样类型的元素

// 数组直接量中的值不必定要是常量,能够是任意的表达式
var base = 1024;
var table = [base, base+1, base+2, base+3];

// 也能够包含对象直接量或其余数组直接量
var b = [[1, {x:1, y:2}], [2, {x:3, y:4}]];

注意,不要忽略数组字面量的最后一个元素,仅以逗号结尾。下面几个案例,在不一样的浏览器下,可能会被识别成2个元素,也有可能识别成3个元素,而形成程序bug。例如:数组

var nums = [,,,];               // 很差的写法
var names = ["stone",,];        // 很差的写法
var colors = ["red","green",];  // 很差的写法

使用 new 关键字建立数组

使用 new 关键字调用构造函数 Array() 是建立数组的另外一种方法,能够用三种方式调用构造函数。例如:浏览器

// 调用时没有参数
var a = new Array();

// 调用时有一个数值参数,它指定长度
var a = new Array(10); 

// 显式指定多个数组元素或者数组的一个非数值元素
var a = new Array(5, 4, 3, 2, 1, "testing");

数组元素的读和写

使用 [] 操做符来访问数组中的一个元素。数组的引用位于方括号的左边。方括号中是一个返回非负整数值的任意表达式。使用该语法既能够读又能够写数组的一个元素。例如:微信

var a = ["world"];     // 从一个元素的数组开始
var value = a[0];      // 读第0个元素
a[1] = 3.14;           // 写第1个元素
var i = 2; 
a[i] = 3;              // 写第2个元素
a[i + 1] = "hello";    // 写第3个元素
a[a[i]] = a[0];        // 读第0个和第2个元素,写第3个元素

请记住,数组是对象的特殊形式,能够为其建立任意名字的属性。但若是使用的属性是数组的索引,数组的特殊行为就是将根据须要更新它们的length属性值。

注意,可使用负数或非整数来索引数组。这种状况下,数值转换为字符串,字符串做为属性名来用。既然名字不是非负整数,它就只能当作常规的对象属性,而非数组的索引。一样,若是凑巧使用了是非负整数的字符串,它就当作数组索引,而非对象属性。当使用的一个浮点数和一个整数相等时状况也是同样的。例如:

a[-1.23] = true;  // 这将建立一个名为"-1.23"的属性
a["1000"] = 0;    // 这是数组的第1001个元素
a[1.000]          // 和 a[1] 相等

事实上数组索引仅仅是对象属性名的一种特殊类型,这意味着 JavaScript 数组没有「越界」错误的概念。当试图查询任何对象中不存在的属性时,不会报错,只会获得 undefined 值。

稀疏数组

稀疏数组就是包含从0开始的不连续索引的数组。一般,数组的 length 属性值表明数组中元素的个数。若是数组是稀疏的,length 属性值大于元素的个数。能够用 Array() 构造函数或简单地指定数组的索引值大于当前的数组长度来建立稀疏数组。

a = new Array(5);   // 数组没有元素,可是 a.length = 5
a = [];             // 建立一个空数组,a.length = 0
a[1000] = 0;        // 添加一个元素,a.length 被自动更新为1001

足够稀疏的数组一般在实现上比稠密的数组更慢、内存利用率更高,在这样的数组中查找元素的时间与常规对象属性的查找时间同样长。

须要注意的是,当省略数组直接量中的值时(使用连续的逗号,好比 [1,,3] ),这时所获得的数组也是稀疏数组,省略掉的值是不存在的:

var a1 = [,'1','2'];    // 此数组长度是3 
var a2 = [undefined];   // 此数组包含一个值为 undefined 的元素 
console.log(0 in a1);   // false,a1 在索引0处没有元素
console.log(0 in a2);   // true,a2 在索引0处有一个值为 undefined 的元素

了解稀疏数组是了解 JavaScript 数组的真实本质的一部分。尽管如此,实际上你所碰到的绝大多数 JavaScript 数组不是稀疏数组。而且,若是你确实碰到了稀疏数组,你的代码极可能像对待非稀疏数组同样来对待它们,只不过它们包含一些 undefined 值。

数组长度

每一个数组有一个 length 属性,就是这个属性使其区别于常规的 JavaScript 对象。针对稠密(也就是非稀疏)数组,length 属性值表明数组中元素的个数。其值比数组中最大的索引大1。例如:

[].length             // 0,数组没有元素
['a','b','c'].length  // 3,最大的索引为2,length 为3

当数组是稀疏的时,length 属性值大于元素的个数。并且关于此咱们能够说的一切也就是数组长度保证大于它每一个元素的索引值。或者,换一种说法,在数组中(不管稀疏与否)确定找不到一个元素的索引值大于或等于它的长度。为了维持此规则不变化,数组有两个特殊的行为。

  • 第一个如同上面的描述:若是为一个数组元素赋值,它的索引 i 大于或等于现有数组的长度时,length 属性的值将设置为 i+1
  • 第二个特殊的行为就是设置 length 属性为一个小于当前长度的非负整数 n 时,当前数组中那些索引值大于或等于 n 的元素将从中删除。例如:
a = [1,2,3,4,5];     // 从5个元素的数组开始
a.length = 3;        // 如今 a 为[1,2,3]
a.length = 0;        // 删除全部的元素。a 为[ ]
a.length = 5;        // 长度为5,可是没有元素,就像 new Array(5)

还能够将数组的 length 属性值设置为大于其当前的长度。实际上这不会向数组中添加新的元素,它只是在数组尾部建立一个空的区域。

在 ECMAScript 5中,能够用 Object.defineProperty() 让数组的 length 属性变成只读的。例如:

a = [1,2,3];                                            // 从3个元素的数组开始
Object.defineProperty(a, "length", {writable: false});  // 让 length 属性只读
a.length = 0;                                           // a 不会改变

数组元素的添加和删除

咱们已经见过添加数组元素最简单的方法,为新索引赋值。例如:

a = []           // 开始是一个空数组
a[0] = "zero";   // 而后向其中添加元素
a[1] = "one";

也可使用 push() 方法在数组末尾增长一个或多个元素。例如:

a = [];                 // 开始是一个空数组
a.push("zero");         // 在末尾添加一个元素。a = ["zero"]
a.push("one", "two");   // 再添加两个元素。a = ["zero", "one", "two"]

能够像删除对象属性同样使用 delete 运算符来删除数组元素。例如:

a = [1,2,3]; 
delete a[1];   // a在索引1的位置再也不有元素
1 in a         // => false: 数组索引1并未在数组中定义
a.length       // => 3: delete操做并不影响数组长度

注意,对一个数组元素使用 delete 不会修改数组的 length 属性,也不会将元素从高索引处移下来填充已删除属性留下的空白。若是从数组中删除一个元素,它就变成稀疏数组。

数组遍历

使用 for 循环是遍历数组元素最多见的方法。例如:

var keys = Object.keys(o);   // 得到 o 对象属性名组成的数组
var values = []              // 在数组中存储匹配属性的值
for(var i = 0; i < keys.length; i++) {  // 对于数组中每一个索引
    var key = keys[i];                  // 得到索引处的键值
    values[i] = o[key];                 // 在 values 数组中保存属性值
}

在嵌套循环或其余性能很是重要的上下文中,能够看到这种基本的数组遍历须要优化,数组的长度应该只查询一次而非每次循环都要查询。例如:

for(var i = 0, len = keys.length; i < len; i++) {
   // 循环体仍然不变
}

这些例子假设数组是稠密的,而且全部的元素都是合法数据。不然,使用数组元素以前应该先检测它们。例如:

for(var i = 0; i < a.length; i++) {
    if (!a[i]) continue;    // 跳过 null、undefined 和不存在的元素
    if (!(i in a)) continue ;   // 跳过不存在的元素
    if (a[i] === undefined) continue;   // 跳过 undefined 和不存在的元素
    // 循环体
}

还可使用 for-in 循环处理稀疏数组。循环每次将一个可枚举的属性名(包括数组索引)赋值给循环变量,不存在的索引将不会遍历到。例如:

for(var index in sparseArray) {
   var value = sparseArray[index];
   // 此处可使用索引和值作一些事情
}

但因为 for-in 循环可以枚举继承的属性名,如添加到 Array.prototype 中的方法。基于这个缘由,在数组上不该该使用 for-in 循环,除非使用额外的检测方法来过滤不想要的属性。例如:

for(var i in a) {
    // 跳过继承的属性
    if (!a.hasOwnProperty(i)) continue;

    // 跳过不是非负整数的 i
    if (String(Math.floor(Math.abs(Number(i)))) !== i) continue;
}

JavaScript 规范容许 for-in 循环以不一样的顺序遍历对象的属性。一般数组元素的遍历实现是升序的,但不能保证必定是这样的。若是数组同时拥有对象属性和数组元素,返回的属性名极可能是按照建立的顺序而非数值的大小顺序。如何处理这个问题的实现,各个浏览器都不相同,若是算法依赖于遍历的顺序,那么最好不要使用 for-in 而用常规的 for 循环。

ECMAScript 5定义了一些遍历数组元素的新方法,按照索引的顺序按个传递给定义的一个函数。这些方法中最经常使用的就是 forEach() 方法。例如:

var data = [1,2,3,4,5];     // 这是须要遍历的数组
var sumOfSquares = 0;       // 要获得数据的平方和
data.forEach(function(x) {  // 把每一个元素传递给此函数
    sumOfSquares += x*x;    // 平方相加
});
console.log(sumOfSquares);  // 55,1 + 4 + 9 + 16 + 25

数组检测

给定一个未知的对象,断定它是否为数组一般很是有用。在 ECMAScript 5中,可使用 Array.isArray() 函数来作这件事情。例如:

Array.isArray([])   // true
Array.isArray({})   // false

可是,在 ECMAScript 5之前,要区分数组和非数组对象很困难。typeof 运算符对数组返回 "object"(而且对于除了函数之外的全部对象都是如此)。instanceof 操做符也只能用于简单的情形。例如:

[] instanceof Array     // true
({}) instanceof Array   // false

使用 instanceof 的问题是在 Web 浏览器中有可能有多个窗体存在。每一个窗体都有本身的 JavaScript 环境,有本身的全局对象。而且,每一个全局对象有本身的一组构造函数。所以一个窗体中的对象将不多是另外窗体中的构造函数的实例。窗体之间的混淆不常发生,但这个问题足已证实 instanceof 操做符不能视为一个可靠的数组检测方法。

解决方案是检查对象的类属性,对数组而言该属性的值老是 "Array",所以在 ECMAScript 3中 isArray() 函数的代码能够这样书写。例如:

var isArray = Array.isArray || function(o) {
    return typeof o === "object" && Object.prototype.toString.call(o) === "[object Array]";
};

数组方法

ECMAScript 3和 ECMAScript 5在 Array.prototype 中定义了一些颇有用的操做数组的方法。

转换方法

全部对象都具备 toLocaleString()toString()valueOf() 方法。其中,调用数组的 toString()valueOf() 方法会返回相同的值,即由数组中每一个值的字符串形式拼接而成的一个以逗号分隔的字符串。实际上,为了建立这个字符串会调用数组每一项的 toString() 方法。例如:

var colors = ["red", "blue", "green"];  // 建立一个包含3个字符串的数组
alert(colors.toString()); // red,blue,green
alert(colors.valueOf());  // red,blue,green
alert(colors);            // red,blue,green

在这里,咱们首先显式地调用了 toString()valueOf() 方法,以便返回数组的字符串表示,每一个值的字符串表示拼接成了一个字符串,中间以逗号分隔。最后一行代码直接将数组传递给了 alert()。因为 alert()要接收字符串参数,因此它会在后台调用 toString() 方法,由此会获得与直接调用 toString() 方法相同的结果。

另外,toLocaleString() 方法常常也会返回与 toString()valueOf() 方法相同的值,但也不老是如此。当调用数组的 toLocaleString() 方法时,它也会建立一个数组值的以逗号分隔的字符串。而与前两个方法惟一的不一样之处在于,这一次为了取得每一项的值,调用的是每一项的 toLocaleString() 方法,而不是 toString() 方法。例如:

var person1 = {
    toLocaleString : function () {
        return "Nikolaos";
    },
    toString : function() {
        return "Nicholas";
    }
};

var person2 = {
    toLocaleString : function () {
        return "Grigorios";
    },
    toString : function() {
        return "Greg";
    }
};

var people = [person1, person2];
alert(people);                           // Nicholas,Greg
alert(people.toString());                // Nicholas,Greg
alert(people.toLocaleString());          // Nikolaos,Grigorios

数组继承的 toLocaleString()toString()valueOf()方法,在默认状况下都会以逗号分隔的字符串的形式返回数组项。而若是使用 join() 方法,则可使用不一样的分隔符来构建这个字符串。join() 方法只接收一个参数,即用做分隔符的字符串,而后返回包含全部数组项的字符串。例如:

var colors = ["red", "green", "blue"];
console.log(colors.join(","));    // red,green,blue
console.log(colors.join("||"));   // red||green||blue

若是数组中的某一项的值是 null 或者 undefined,那么该值在 join()toLocaleString()toString()valueOf() 方法返回的结果中以空字符串表示。

栈方法

栈是一种 LIFO(Last-In-First-Out,后进先出)的数据结构,也就是最新添加的项最先被移除。push() 方法能够接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。而 pop() 方法则从数组末尾移除最后一项,减小数组的 length 值,而后返回移除的项。结合 push()pop() 方法,就能够像栈同样使用数组。例如:

var colors = [];                            // 建立一个数组
var count = colors.push("red", "green");    // 推入两项
console.log(count);                         // 2,数组的长度

count = colors.push("black");               // 推入另外一项
console.log(count);                         // 3,数组的长度

var item = colors.pop();                    // 取得最后一项
console.log(item);                          // "black"
console.log(colors.length);                 // 2,数组的长度

队列方法

队列是一种 FIFO(First-In-First-Out,先进先出)的数据结构,队列在列表的末端添加项,从列表的前端移除项。shift() 方法则从数组前端移除第一项,减小数组的 length 值,而后返回移除的项。结合 push()shift() 方法,就能够像队列同样使用数组。例如:

var colors = [];                            // 建立一个数组
var count = colors.push("red", "green");    // 推入两项
console.log(count);                         // 2,数组的长度

count = colors.push("black");               // 推入另外一项
console.log(count);                         // 3,数组的长度

var item = colors.shift();                  // 取得第一项
console.log(item);                          // "red"
console.log(colors.length);                 // 2,数组的长度

JavaScipt 还为数组提供了一个 unshift() 方法。顾名思义,unshift()shift() 的用途相反,它能在数组前端添加任意个项并返回新数组的长度。所以,同时使用 unshift()pop() 方法,能够从相反的方向来模拟队列,即在数组的前端添加项,从数组末端移除项。

重排序方法

数组中有两个重排序的方法:reverse()sort()reverse() 方法能够反转数组元素的顺序。例如:

var values = [1, 2, 3, 4, 5];
values.reverse();
console.log(values);  // 5,4,3,2,1

sort() 方法能够按升序排列数组元素(即最小的值位于最前面,最大的值排在最后面)。sort() 方法在排序的过程当中会调用每一个数组元素的 toString(),而后比较获得的字符串,以肯定如何排序。即便数组中的每一项都是数值,sort() 方法比较的也是字符串,例如:

var values = [0, 1, 5, 10, 15];
values.sort();
console.log(values);     // 0,1,10,15,5

这种排序方式在不少状况下都不是最佳方案,所以 sort() 方法能够接收一个比较函数做为参数,以便咱们指定哪一个值位于哪一个值的前面,如下就是一个简单的比较函数。例如:

function compare(value1, value2) {
    if (value1 < value2) {
        return -1;
    } else if (value1 > value2) {
        return 1;
    } else {
        return 0;
    }
}

这个比较函数接收两个参数,若是第一个参数应该位于第二个以前则返回一个负数,若是两个参数相等则返回0,若是第一个参数应该位于第二个以后则返回一个正数。它能够适用于大多数状况,只要将其做为参数传递给 sort() 方法便可。例如:

var values = [10, 5, 1, 0, 15];
values.sort(compare);
console.log(values);   // 0,1,5,10,15

对于数值类型或者其 valueOf() 方法会返回数值类型的对象类型,可使用一个更简单的比较函数。这个函数只要用第二个值减第一个值便可。例如:

function compare(value1, value2){
    return value2 - value1;
}

因为比较函数经过返回一个小于零、等于零或大于零的值来影响排序结果,所以减法操做就能够适当地处理全部这些状况。

操做方法

JavaScript 为操做已经包含在数组中的元素提供了不少方法。其中,concat() 方法能够基于当前数组中的全部项建立一个新数组。具体来讲,这个方法会先建立当前数组一个副本,而后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat() 方法传递参数的状况下,它只是复制当前数组并返回副本。若是传递给 concat() 方法的是一或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中。若是传递的值不是数组,这些值就会被简单地添加到结果数组的末尾。例如:

var colors = ["red", "green", "blue"];
var colors2 = colors.concat("yellow", ["black", "brown"]);

console.log(colors);     // red,green,blue
console.log(colors2);    // red,green,blue,yellow,black,brown

下一个方法是 slice(),它可以基于当前数组中的一或多个项建立一个新数组。slice() 方法能够接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的状况下,slice() 方法返回从该参数指定位置开始到当前数组末尾的全部项。若是有两个参数,该方法返回起始和结束位置之间的项,但不包括结束位置的项。注意,slice() 方法不会影响原始数组。例如:

var colors = ["red", "green", "blue", "yellow", "purple"];
var colors2 = colors.slice(1);
var colors3 = colors.slice(1,4);

console.log(colors2);   // green,blue,yellow,purple
console.log(colors3);   // green,blue,yellow

若是 slice() 方法的参数中有一个负数,则用数组长度加上该数来肯定相应的位置。例如,在一个包含5项的数组上调用 slice(-2,-1) 与调用 slice(3,4) 获得的结果相同。若是结束位置小于起始位置,则返回空数组。

下一个方法是 splice(),它的主要用途是向数组的中部插入元素,主要有如下3种使用方式。

  • 删除:能够删除任意数量的项,只需指定2个参数:起始位置和要删除元素的数量。例如,splice(0,2) 会删除数组中的前两项。
  • 插入:能够向指定位置插入任意数量的项,只需提供3个参数:起始位置、0(要删除元素的数量)和要插入的元素。若是要插入多个元素,能够再传入第4、第五,以致任意多个元素。例如,splice(2,0,"red","green") 会从当前数组的位置2开始插入字符串 "red""green"
  • 替换:能够向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3个参数:起始位置、要删除元素的数量和要插入的元素。插入的项数没必要与删除的项数相等。例如,splice (2,1,"red","green")会删除当前数组位置2的项,而后再从位置2开始插入字符串 "red""green"

splice() 方法始终都会返回一个数组,该数组中包含从原始数组中删除的项(若是没有删除任何项,则返回一个空数组)。下面的代码展现了上述3种使用 splice() 方法的方式。例如:

var colors = ["red", "green", "blue"];
var removed = colors.splice(0,1);       // 删除第一项
console.log(colors);                    // green,blue
console.log(removed);                   // red,返回的数组中只包含一项

removed = colors.splice(1, 0, "yellow", "orange");  // 从位置1开始插入两项
console.log(colors);                    // green,yellow,orange,blue
console.log(removed);                   // 返回的是一个空数组

removed = colors.splice(1, 1, "red", "purple");     // 插入两项,删除一项
console.log(colors);                    // green,red,purple,orange,blue
console.log(removed);                   // yellow,返回的数组中只包含一项

位置方法

ECMAScript 5为数组实例添加了两个位置方法:indexOf()lastIndexOf()。这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中,indexOf() 方法从数组的开头(位置0)开始向后查找,lastIndexOf() 方法则从数组的末尾开始向前查找。

这两个方法都返回要查找的项在数组中的位置,或者在没找到的状况下返回 -1。在比较第一个参数与数组中的每一项时,会使用全等操做符;也就是说,要求查找的项必须严格相等(就像使用 === 同样)。例如:

var numbers = [1,2,3,4,5,4,3,2,1];
console.log(numbers.indexOf(4));          // 3
console.log(numbers.lastIndexOf(4));      // 5
console.log(numbers.indexOf(4, 4));       // 5
console.log(numbers.lastIndexOf(4, 4));   // 3

var person = { name: "Nicholas" };
var people = [{ name: "Nicholas" }];
var morePeople = [person];
console.log(people.indexOf(person));      // -1
console.log(morePeople.indexOf(person));  // 0

迭代方法

ECMAScript 5为数组定义了5个迭代方法。每一个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的做用域对象。传入这些方法中的函数会接收三个参数:数组项的值、该项在数组中的位置和数组对象自己。根据使用的方法不一样,这个函数执行后的返回值可能会也可能不会影响访问的返回值。如下是这5个迭代方法的做用。

  • every(),对数组中的每一项运行给定函数,若是该函数对每一项都返回 true ,则返回 true
  • filter(),对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
  • forEach(),对数组中的每一项运行给定函数。这个方法没有返回值。
  • map(),对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
  • some(),对数组中的每一项运行给定函数,若是该函数对任一项返回 true ,则返回 true

以上方法都不会修改数组中的包含的值。在这些方法中,最类似的是 every()some(),它们都用于查询数组中的项是否知足某个条件。对 every() 来讲,传入的函数必须对每一项都返回 true,这个方法才返回 true;不然,它就返回 false。而 some()方法则是只要传入的函数对数组中的某一项返回 true,就会返回 true。例如:

var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item, index, array){
    return (item > 2); 
});
console.log(everyResult);   // false

var someResult = numbers.some(function(item, index, array){
    return (item > 2);
});
console.log(someResult);    // true

下面再看一看 filter() 函数,它利用指定的函数肯定是否在返回的数组中包含的某一项。例如:

var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item, index, array){
    return (item > 2);
});
console.log(filterResult);  // [3,4,5,4,3]

map() 也返回一个数组,而这个数组的每一项都是在原始数组中的对应项上运行传入函数的结果。例如,能够给数组中的每一项乘以2,而后返回这些乘积组成的数组。例如:

var numbers = [1,2,3,4,5,4,3,2,1];
var mapResult = numbers.map(function(item, index, array){
    return item * 2;
});
console.log(mapResult);     // [2,4,6,8,10,8,6,4,2]

最后一个方法是 forEach(),它只是对数组中的每一项运行传入的函数。这个方法没有返回值,本质上与使用 for 循环迭代数组同样。例如:

var numbers = [1,2,3,4,5,4,3,2,1];
numbers.forEach(function(item, index, array){
    //执行某些操做 
});

 缩小方法

ECMAScript 5还新增了两个缩小数组的方法:reduce()reduceRight()。这两个方法都会迭代数组的全部项,而后构建一个最终返回的值。其中,reduce() 方法从数组的第一项开始,逐个遍历到最后。而 reduceRight() 则从数组的最后一项开始,向前遍历到第一项。

这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)做为缩小基础的初始值。传给 reduce()reduceRight() 的函数接收4个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会做为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,所以第一个参数是数组的第一项,第二个参数就是数组的第二项。

使用 reduce() 方法能够执行求数组中全部值之和的操做。例如:

var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
    return prev + cur; 
});
console.log(sum); // 15

第一次执行回调函数,prev是1,cur是2。第二次,prev是3(1加2的结果),cur是3(数组的第三项)。这个过程会持续到把数组中的每一项都访问一遍,最后返回结果。

reduceRight() 的做用相似,只不过方向相反而已。例如:

var values = [1,2,3,4,5];
var sum = values.reduceRight(function(prev, cur, index, array){
    return prev + cur;
});
console.log(sum); // 15

使用 reduce() 仍是 reduceRight(),主要取决于要从哪头开始遍历数组。除此以外,它们彻底相同。

关卡

完成下面3个数组去重方法。

// 挑战一,一维数组
var arr = [2,3,4,2,3,5,6,4,3,2];
var unique = function(arr){
    // 待实现方法体
}
console.log(unique(arr)); // [2,3,4,5,6]
// 挑战二,二维数组
var arr = [2,3,4,[2,3,4,5],3,5,[2,3,4,2],4,3,6,2];
var unique = function(arr){
    // 待实现方法体
}
console.log(unique(arr)); // [2,3,4,5,6]
// 挑战三,三维数组或 n 维数组
var arr = [2,3,4,[2,3,[2,3,4,2],5],3,5,[2,3,[2,3,4,2],2],4,3,6,2];
var unique = function(arr){
    // 待实现方法体
}
console.log(unique(arr)); // [2,3,4,5,6]

更多

关注微信公众号「劼哥舍」回复「答案」,获取关卡详解。
关注 https://github.com/stone0090/javascript-lessons,获取最新动态。

相关文章
相关标签/搜索