es6学习笔记之一 数组篇

es6也称为es2015,是EcmaScript的第六个大版本,虽然已经推出很久了,但是我还是发现有很多码农仍然不使用es6的一些新特性。
我觉的其实没必要对这些新功能感到恐惧,要知道任何新功能的加入都是为了使这门编程语言更加完善。而且,能进入es6的新功能大多
是一些在业务中需求量很大的功能,而且很多库也都有着自己的实现。这个系列就是要向大家介绍一下es6为我们带来的一些新功能,而
且我觉的掌握这些功能基本能搞定平时80%的业务需求。

众所周知,array即数组是任何一门编程语言必不可少的数据类型或数据集合,JS也不例外,每个版本中都会为array增加许多新属性和
新方法。这篇文章会简单介绍一下es6为我们带来的7种新的array方法,如果你用过类似于underscore或者lodash这一类功能库,那
么其中的很多方法你一定不会感到陌生。

1. ForEach

我敢保证你在操作数组的时候一定使用过for循环,而今天我们将要介绍的这7种方法设计的目的就是让你摒弃for循环。为什么要这样做
呢?我觉的答案有三点:1. 简化代码,增加代码的可读性。2. 增加代码执行效率。3.提高你的逼格(这很重要)。

ForEach这个方法和for循环非常相似,可以用来遍历数组,大概长这个样子
arr.map(callback)
callback中的第一个参数为遍历的每一个元素,index为该元素的索引

让我们看一个简单的例子吧:

1
2
3
4
5
6
7

var numbers = [1, 2, 3, 4];

numbers.forEach(function(number){
console.log(number)
})


这里的输出是
1
2
3
4
1,0 
2,1
3,2
4,3

2. map

map方法一般接受一个函数作为参数,返回一个新数组。大概就是这样:

arr.map(callback)

item就是arr遍历到的每个元素,相当于for循环中的arr[i],让我们来看一个简单的例子:

1
2
3
4
5
6
7
8
9

var numbers = [1, 2, 3, 4];

var doubledNumbers = numbers.map(function(number){
return number * 2;
});

console.log(doubledNumbers)//返回 [2, 4, 6, 8]

这个例子很简单,就是我们有一个数组,现在我想把每个数组里的元素X2,然后赋值给一个新书组。但是还是有三个点请大家注意一下:1.
callback函数一定要有return,否则就会返回undefined。2. map方法会返回一个新书组,从而做到不改变原数组的值,
即immutable。3. callback不一定是匿名函数,所以我们还可以这样:

1
2
3
4
5
6
7
8
9
10
11

var numbers = [1, 2, 3, 4];

function doubleNumbers(number){
return number * 2;
}

var doubledNumbers = numbers.map(doubleNumbers);

console.log(doubledNumbers)//返回 [2, 4, 6, 8]

我们可以看到,返回的结果是完全一样的。

3. filter

filter是用来过滤数组的,它会给出一个条件,然后遍历数组,当数组的某个元素符合这个条件,这个元素就会被保留,反之就会被过滤掉,
然后返回一个新的数组。

filter的用法大概是这样
arr.filter(callback)
这里,callback通常接收一个参数,即被遍历到的元素,我们来看一个例子吧:

1
2
3
4
5
6
7
var numbers = [1, 2, 3, 4];

var evenNumbers = numbers.filter(function(number){
return number % 2 === 0;
});

console.log(evenNumbers)

这个例子就是从numbers里面找到所有偶数元素,然后赋值给新数组evenNumbers。这里有两点需要注意:1. return一定不要忘了,2.
filter不会改变原数组,而是会返回一个新数组。这段代码运行的结果是 [2, 4]。

4. find

find方法会接受一个条件,然后遍历数组,返回第一个符合该条件的元素,大概这样:
arr.find(callback)
同样,我们来看个例子吧:

1
2
3
4
5
6
var numbers = [1, 2, 3, 4];

var numberFour = numbers.find(function(number){
return number === 4;
})
console.log(numberFour);

这段代码返回的结果是4。要注意的一个问题是,当你的数组里有多个符合该条件的元素时,find方法只会返回第一个,而且,find方法只会
返回一个元素,而不是一个数组。举个例子:

1
2
3
4
5
6
7
var numbers = [1, 2, 3, 4];

var numberFour = numbers.find(function(number){
return number >= 3;
});

console.log(numberFour);

这段代码返回的不是[3,4],而是3,因为3是第一个符合条件的元素。如果你想让它执行上面的逻辑的话,那么请使用filter。

5. every/some

把这两个方法放在一起说是因为他们非常相似,他们都是接受一个条件,如果这个数组的每一个元素都符合这个条件,every就会返回true。
如果这个数组中有任何一个元素符合这个条件,那么some就会返回true。所以我们可以说,我们给出一个条件,如果every的返回值是true
的话那么some一定也是true。

我们来看个例子:

1
2
3
4
5
var numbers = [1, 2, 3, 4];
var allGreaterThan0 = numbers.every(function(number){
return number>0;
});
console.log(allGreaterThan0);

这段代码的返回结果是true,因为所有的元素都大于0。但是如果我们把条件稍微改改,改成number>1,那么就会返回false,因为第一个
元素是1,1>1是false。

1
2
3
4
5
6
var numbers = [1, 2, 3, 4];
var allGreaterThan3 = numbers.some(function(number){
return number>3;
});

console.log(allGreaterThan3);

这段代码的返回结果是true,因为最后一个元素4大于3。

every和some这两个方法要注意一个点:那就是这个方法不会返回任何数组或者值,只会返回true或false,所以我们可以在判断的时候使用
这两种方法,比如if(arr.some(callback))...,我们可以在外面定义callback方法,也可以用箭头函数(=>)来定义,之后的文章
会介绍箭头函数。

6. reduce

reduce方法是这7个方法中最绕的一个,不过不用担心,我们会详细的说说这个复(dan)杂(teng)的方法,尽量让大家明白。
reduce的作用是接受一个初始值和一个方法,对一个数组进行遍历,最后生成一个新的值,数据类型是初始值的数据类型,这么说肯定很绕,我
们来看几个例子:

1
2
3
4
5
6
7
8
var numbers = [1, 2, 3, 4];

var total = numbers.reduce(function(previous, number){
previous += number;
return previous;
}, 0)

console.log(total);

这段代码是把元素里面所有的值累加,并且返回所有元素相加的和,也就是10。我们来分块说一下,首先,reduce方法接受两个参数,第一个参数
是一个方法,我们一会再回来说它,先说第二个参数,这个参数是返回值的初始值,比如,再刚才那个例子里,如果我们的初始值给10而不是0,那
么结果就是20。回头来我们说说第一个参数,这个参数是reduce的规则,同样接受两个参数,以上面的例子来说,previous就是每次返回的值,
换句话说,我们每次都是都会返回这个值,用来做下一步的计算,而且reduce函数最后返回的值其实也是这个previous。第二个参数number则是
数组每次迭代的元素。也就是1,2,3,4。

我们来看个图表,理解一下reduce是怎么工作的

1
2
3
4
5
6
7
8
9
10
迭代第一个元素之前:
previous: 0, number: 1
迭代第一个元素后:
previous: 0+1=1, number: 1
迭代第二个元素后:
previous: 1+2=3, number: 2
迭代第三个元素后:
previous: 3+3=6, number: 3
迭代第四个元素后:
previous: 6+4=10, number: 4

现在清晰很多了吧,对,reduce的第一个参数,也就是那个回调函数会执行4次,为每一个元素执行一次,最后返回总的值。

现在,我们来看两个非常实用的例子,在各种面试题里面非常常见:

  1. 数组去重,就是说,给你个数组,[1, 1, 2, 3, 4, 4]大概这样,让你来写个方法,去掉重复的元素,返回[1, 2, 3, 4]。我建议你可以先不要往后
    看,自己试试,如果想不出来或者不知道怎么用到reduce方法,请继续往后看。

分析:
这里,首先我们要用reduce方法,那么我们要首先确定初始值,这里我们需要一个空数组,然后通过reduce的第一个参数那个callback每次往数组里
push。这步做完,我们应该有了这么一个轮廓:

1
2
3
4
5
6
7
8
9
var numbers = [1, 1, 2, 3, 4, 4];

function unique(array) {
return array.reduce((previous, number)=>{

}, []);
}

unique(numbers);

接下来,我们需要想想每次我们需要做什么,我们需要一个方法,接受每次传进来的数,然后看是不是包含在previous数组里,
确定某个元素是否包含在数组里有很多种方法,这里,我们可以用some方法,因为previous也是一个数组,但是,值得注意的
一点是这里我们需要的不是包含在数组里,而是不包含在previous数组里,所以我们需要取反。最后,如果取反得到的是true
的话,就说明这个数字不包含在previous数组里,所以,我们要把它push进previous。然后返回新的previous数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var numbers = [1, 1, 2, 3, 4, 4];

function unique(array) {
return array.reduce((previous, number)=>{
var eql = previous.some(function(num){
return number === num;
});
if(!eql){
previous.push(number)
}
return previous;
}, [])
}

unique(numbers)

这也就是最后的代码,当然,这段代码其实写的并不完美,有很多地方可以优化,但是这需要用到其他的es6的一些方法,比如…操作符
以及=>箭头函数。在我们接触这些内容以前,先保持这样吧,如果对刚才我说的那两个概念有了解的同学可以自己尝试一下优化这段代码。
还有一点要注意的是,我写的这个并不是数组去重的最好解法,有兴趣的同学可以看一下es6中的Set。

  1. 检查括号

我们在刚开始学编程的时候,尤其是一开始用记事本写代码的时候,肯定会碰到这么个问题,括号的数量对不上,比如少半个左括号或者半
个右括号。现在,我们要写一个方法,用来检查一个字符串中的口号是否对称。举几个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function bracketMatch(str){

}
var str1 = '()';
var str2 = '(())';
var str3 = '()()';
var str4 = '((()))()';
var str5 = '((aaa))';

var str6 = '((())';
var str7 = '(((aaa))';
var str8 = '())';
var str9 = ')(';
var str10 = ')()(';

bracketMatch(str1) //true
bracketMatch(str2) //true
bracketMatch(str3) //true
bracketMatch(str4) //true
bracketMatch(str5) //true

bracketMatch(str6) //false
bracketMatch(str7) //false
bracketMatch(str8) //false
bracketMatch(str9) //false
bracketMatch(str10) //false

还是和刚才那个例子一样,请大家先自己想一下,试着动手做做看,然后再往下继续看。

分析:

我们首先要做的一件事是把传入function的str转换成数组,这样才能调用数组的方法,所以我们第一步需要这么做
var strArr = str.split('')。这样依赖我们会得到一个数组,每个元素就是str的每个字符。下一步我们想
一下,怎么才算平衡,假如我们把左括号(算作+1,右括号)算作-1的话,一个平衡的字符串我们需要让的每个元素
相加最后得到0,反之则说明不平衡。好,那么根据这个逻辑,我们想到应该使用reduce方法,把整个数组进行运算最
后得到一个整数。

1
2
3
4
5
6
7
8
function bracketMatch(str){
var strArr = str.split('');
var result = strArr.reduce(function(previous, char){
if(char==='(') return ++previous;
if(char===')') return --previous;
}, 0);
return !result
}

这里我们在reduce方法里先给一个0作为初始值,然后每一次迭代改变这个值,如果是左括号就+1,如果是右括号就减
1,这样一来我们得到的结果如果是0的话,就说明括号是对称的,就应该返回true,相反的,我们应该返回false。所以
我们在最后要给result取反,因为0是false,一切非0的数字都是true。

这里我们可以看到,80%的需求已经搞定了,现在我们还有两个问题:1.如果括号中参杂着其他字符,我们没办法处理。2.
如果先写右括号,然后在写左括号,也就是这样)(,我们的返回值仍然是true,因为reduce得到的数组也是0。下面我们
就要根据这两种情况对代码进行改进。

先说第一个问题,如果我们看到不是括号的内容,我们其实什么都不用做,只要让previous直接返回即可。也就是这样在
两个if之后加这么一条return previous;问题就解决了。然后我们反过来说第二个问题。我们来想一下,)(情况在我
们的逻辑里会出现什么事,对,没错,在走第一个右括号时,previous这个值会小于0。所以我们就从这一特性下手,我们
可以认为,一旦previous的值小于0,就说明)(情况已经发生了,所以后面其实也没有必要再运算下去了,不是么?所以我
们可以在所有的if条件前加一句if(previous<0) return previous;,这样一来,只要有一次previous的值等于
-1的话,那么之后previous的值会一直满足<0这个条件,也就是说previous的值一直会是-1。

整个代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
function bracketMatch(str){
var strArr = str.split('');
console.log(strArr);
var result = strArr.reduce(function(previous, char){
if(previous<0) return previous;
if(char==='(') return ++previous;
if(char===')') return --previous;
return previous;
}, 0);
return !result
}

7.总结

这里为大家介绍了7种es6中数组新增的方法,这些方法有很多通性,而且这里也为大家介绍了两个比较好的理念,第一就是尽量不
要直接修改数组的值,而是用一些方法返回新的值。还有一个就是尽量不要在代码中只用for循环进行迭代,其实大家应该已经发现
了,这些功能其实都是我们平时很需要的功能,而且根本没有什么高深的。其实我们这个系列的宗旨就是给大家介绍一些es6中最实
用而且最常见的新功能。