上篇文章我們介紹了ES6的多行字符串表示方法,map,set,for...of循環(huán),rest參數(shù)以及l(fā)et和const,這次我們接著來看看ES6有哪些過人之處。
ECMAScript 6為數(shù)組增添的新方法:map(),reduce(),filter()
①map()
語法:arr.map(函數(shù));
功能:相當于讓arr的每個數(shù)據(jù)執(zhí)行了一次()中的方法,例:
function add(a){
return b =a*a;
}
var arr = [1,2,3,4];
var newArr = arr.map(add);
console.log( newArr);

②reduce()
語法:arr.reduce(函數(shù));
功能:把一個函數(shù)作用在arr的每一個元素上,它必須接收兩個參數(shù),reduce()把結果繼續(xù)和序列的下一個元素做累積計算。例:要把[1,2,3,4,5,6]變換成整數(shù)123456,就可以用reduce()做到
function changeNumber(x,y){
return x * 10 + y;
}
var arr = [1,2,3,4,5,6];
var newArr = arr.reduce(changeNumber);
console.log(newArr);
③filter()
語法:arr.filter(函數(shù));
功能:用于把Array的某些元素過濾掉,然后返回剩下的元素,和map()類似,Array的filter()也接收一個函數(shù)。和map()不同的是,filter()把傳入的函數(shù)依次作用于每個元素,然后根據(jù)返回值是true還是false決定保留還是丟棄該元素。例:利用filter()刪除數(shù)組中的偶數(shù)項
function deleteOushu(x){
return x % 2 !== 0;
}
var arr = [1,2,3,4,5,6,7,8,9,10];
var newArr = arr.filter(deleteOushu);
console.log(newArr);

filter()接收的回調(diào)函數(shù)可以有多個參數(shù)。第一個參數(shù)表示Array的某個元素。另外兩個參數(shù),表示元素的位置和數(shù)組本身:
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
console.log(element);
// 依次打印'A', 'B', 'C'
console.log(index);
// 依次打印0, 1, 2
console.log(self);
// self就是變量arr
return true;
});
我們還可以利用filter()巧妙的去除數(shù)組的重復項:
var arr = ["a","b","c","b","a","d"];
var newArr = arr.filter(function(element,index,self){
return self.indexOf(element)===index;
});

箭頭函數(shù)
ES6標準新增了一種新的函數(shù):Arrow Function(箭頭函數(shù))
x => x * x;
這個箭頭函數(shù)等價于
function (x){
return x * x ;
}
箭頭函數(shù)相當于匿名函數(shù),并且簡化了函數(shù)定義。箭頭函數(shù)有兩種格式,一種像上面的,只包含一個表達式,連{ ... }和return都省略掉了。還有一種可以包含多條語句,這時候就不能省略{ ... }和return:
x => {
if(x>0) {
return x * x;
}
else{
return -x * x;
}
};
如果參數(shù)不是一個,就需要用括號()括起來:
(x,y) => x * x + y * y;
無參數(shù)時就用():
() => 1;
可變參數(shù):
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
如果要返回一個對象就要注意,如果是單表達式,這么寫的話會報錯:
x => { foo: x } // SyntaxError
因為和函數(shù)體的{ ... }有語法沖突,所以要改為:
x => ({ foo: x })
箭頭函數(shù)看上去是匿名函數(shù)的一種簡寫,但實際上,箭頭函數(shù)和匿名函數(shù)有個明顯的區(qū)別:箭頭函數(shù)內(nèi)部的this是詞法作用域,由上下文確定。箭頭函數(shù)完全修復了this的指向,this總是指向詞法作用域,也就是外層調(diào)用者obj。由于this在箭頭函數(shù)中已經(jīng)按照詞法作用域綁定了,所以,用call()或者apply()調(diào)用箭頭函數(shù)時,無法對this進行綁定,this取決于箭頭函數(shù)在哪定義,this不可變,在函數(shù)內(nèi)部這個執(zhí)行環(huán)境中相當于常量。
箭頭函數(shù)沒有arguments,不能通過arguments對象訪問傳入?yún)?shù),只能使用顯示命名或者其他特性完成。
箭頭函數(shù)不能使用new關鍵字來實例化對象,不然會報錯。
箭頭函數(shù)跟普通函數(shù)的區(qū)別,什么是匿名函數(shù)
(1)不需要function
(2)省略return
(3)繼承當前上下文的關鍵字(定義時所在對象)
( 4)不能用作構造函數(shù)
(5)不能用yeild
(6)this固有化
(7)不能使用call,apply,bind去改變
匿名函數(shù):沒有名字的函數(shù)表達式,可以實現(xiàn)動態(tài)編程。
generator(生成器)
一個generator看上去像一個函數(shù),但可以返回多次。函數(shù)執(zhí)行過程中,如果沒有遇到return語句(函數(shù)末尾如果沒有return,就是隱含的return undefined;),控制權無法交回被調(diào)用的代碼。generator跟函數(shù)很像,定義如下:
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
和函數(shù)不同的是generator由function定義(注意多出的號),并且除了return語句,還可以用yield返回多次。例:編寫一個產(chǎn)生斐波那契數(shù)列的函數(shù),可以這么寫:
function fib(max) {
var t=0;
var a=0;
var b=1;
var arr=[0, 1];
while (arr.length < max) {
t = a + b;
a = b;
b = t;
arr.push(t);
}
return arr;
}
fib(5); // 測試結果[0, 1, 1, 2, 3]
函數(shù)只能返回一次,必須返回一個Array。generator就可以一次返回一個數(shù),不斷返回多次。用generator改寫如下:
function* fib(max) {
var t=0;
var a=0;
var b=1;
var n=1;
while (n < max) {
yield a;
t = a + b;
a = b;
b = t;
n++;
}
return a;
}
fib(5); // fib{[[GeneratorStatus]]: "suspended",[[GeneratorReceiver]]:Window}
fib(5)僅僅是創(chuàng)建了一個generator對象,還沒有去執(zhí)行它
調(diào)用generator對象有兩個方法,一是不斷地調(diào)用next()方法:
var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: true}
value就是yield的返回值,done表示這個generator是否已經(jīng)執(zhí)行結束了。為true時value就是return的返回值,并且generator對象就已經(jīng)全部執(zhí)行完畢,不要再繼續(xù)調(diào)用next()了。
第二個方法是直接用for ... of循環(huán)迭代generator對象,這種方式不需要我們自己判斷done:
for (var x of fib(5)) {
console.log(x); // 依次輸出0, 1, 1, 2, 3
}
class繼承
繼承中我們看到了JavaScript的對象模型是基于原型實現(xiàn)的,特點是簡單,缺點是理解起來比傳統(tǒng)的類-實例模型要困難,最大的缺點是繼承的實現(xiàn)需要編寫大量代碼,并且需要正確實現(xiàn)原型鏈。新的關鍵字class從ES6開始正式被引入到JavaScript中。class的目的就是讓定義類更簡單。
我們先回顧用函數(shù)實現(xiàn)Student的方法:
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
如果用新的class關鍵字來編寫Student,可以這樣寫:
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
比較一下就可以發(fā)現(xiàn),class的定義包含了構造函數(shù)constructor和定義在原型對象上的函數(shù)hello()(注意沒有function關鍵字),這樣就避免了Student.prototype.hello = function () {...}這樣分散的代碼。
最后,創(chuàng)建一個Student對象
var xiaoming = new Student('小明');
xiaoming.hello();
用class定義對象的另一個巨大的好處是繼承更方便了。想一想我們從Student派生一個PrimaryStudent需要編寫的代碼量?,F(xiàn)在,原型繼承的中間對象,原型對象的構造函數(shù)等等都不需要考慮了,直接通過extends來實現(xiàn):
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name);
// 記得用super調(diào)用父類的構造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
注意PrimaryStudent的定義也是class關鍵字實現(xiàn)的,而extends則表示原型鏈對象來自Student。子類的構造函數(shù)可能會與父類不太相同,例如,PrimaryStudent需要name和grade兩個參數(shù),并且需要通過super(name)來調(diào)用父類的構造函數(shù),否則父類的name屬性無法正常初始化。
PrimaryStudent已經(jīng)自動獲得了父類Student的hello方法,我們又在子類中定義了新的myGrade方法。
ES6引入的class和原有的JavaScript原型繼承有什么區(qū)別呢?實際上它們沒有任何區(qū)別,class的作用就是讓JavaScript引擎去實現(xiàn)原來需要我們自己編寫的原型鏈代碼。簡而言之,用class的好處就是極大地簡化了原型鏈代碼。