ECMAScript 6新特性印象之一:新語法

前記

按照規劃,明年年中,ECMAScript 6(ES6)就要正式發佈了。javascript

最近抽空看了Dr. Axel Rauschmayer的幾篇文章和演講PPT,對新特性有了些瞭解。html

趁沒忘,抓緊記錄下,夾雜本身的感覺。java

計劃分三部分:git

  1. 新語法
  2. 面對對象和模塊化
  3. 標準庫擴充

參考瞭如下文章/PPT:es6

其餘文章:github

整體印象

的確是「design by champions」。各類爲了代碼書寫效率進行的優化,借鑑了近年各類「新」語言的優秀特性,靈活性大大提高,閱讀難度也提高了……編程

不過,熟悉Ruby的看了這些會放心很多吧。segmentfault

新語法

1.塊級做用域 關鍵字let, const數組

function order(x, y) {
    if (x > y) {
        let tmp = x;
        x = y;
        y = tmp;
    }
    console.log(tmp === x); // 引用錯誤:tmp此時未定義  
    return [x,y];
}

JS終於有了塊級做用域變量。雖然在代碼結構層面沒有太大的做用(之前沒有時也活得很好麼,雖然不怎麼舒服),但會讓代碼更加準確,更易於閱讀。數據結構

今年夏天發佈的Swift中也增長了let關鍵字,雖然有些許區別,但目的應該是差很少——提高代碼可讀性。

2.對象字面量的屬性賦值簡寫 property value shorthand

let first = 'Bob';
let last = 'Dylan';
let singer = { first, last };
console.log(singer.first + " " + singer.last); // Bob Dylan

這對於常用對象做爲配置屬性參數的苦主來講,算個小小的撫慰了。估計重複添加同一屬性會報錯吧,沒有驗證。

3.方法定義 Method definitions

let obj = {
    myMethod(arg0, arg1) {
        ...
    }
};

避免了在對象定義中出現function關鍵字,更加清晰明確地分離出函數的三種用途。

4.賦值解構 Destructuring

let singer = { first: "Bob", last: "Dylan" };
let { first: f, last: l } = singer; // 至關於 f = "Bob", l = "Dylan"

依然是爲了方便。之後代碼頭部的「變量定義區域」不會有太多行了。

數組也是能夠的,下面這個例子特別棒:

let [all, year, month, day] =
    /^(\d\d\d\d)-(\d\d)-(\d\d)$/.exec("2014-08-31");

let [x, y] = [1, 2, 3]; // x = 1, y = 2

固然也能夠這樣,但有些……:

function f([x]) {...} // 參數定義
f(['Blonde on Blonde']);

下面是幾種錯誤用法(Refutable):

let { a: x, b: y } = {a: 3}; // TypeError
let [x, y] = ['a']; // TypeError

更重要的是,支持默認值,在形式不匹配或目標值undefined時有效:

let { a: x, b: y=5 } = {a: 3, b: undefined }; // x = 3, y = 5
let [x, y='b'] = ['a']; // x = 'a', y = 'b'

5.函數的多項返回值 Multiple return values

function findSong(songs, songTitle) {
    for (let trackNumber = 0; trackNumber < songs.length; trackNumber++) {
        let song = songs[trackNumber];
        if(songTitle ===song.title) {
            return {song, trackNumber};
        }
    }
    return {song: undefined, trackNumber: -1}
}

let songList = ["Tombstone blues", "Don't think twice", "North country girl"];

let {song, trackNumber} = findSong(songList, "North country girl"); // song = "North country girl", trackNumber = 2;

由於賦值解構,因此也能夠這樣:

let {song} = findSong(...);
let {trackNumber} = findSong(...);
let {trackNumber, song} = findSong(...); // 變量順序不重要

其實就是返回個對象。

但也有個問題,變量名必定要與函數返回對象的屬性名相同,這能夠會是一個別扭點。

6.函數參數 - 默認值

function findArtist(name='', genre='') {
    ...
}

沒什麼好說的,之後不用再寫var option = option || {}了。

7.函數參數 - 參數打包 Rest parameters

function createArtistProfile(name, ...details) {
    .. // details是個數組
}

因此,之後也不須要arguments了。不過,看例子只是「1,rest」,不知可不能夠「1,2,3,rest」。

8.函數參數 - 數組展開 Spread parameters

Math.max(...[1,11,111]); // 111

算是參數打包的逆操做,之後不用寫[1,2,3].apply(Math.max)這類代碼了。

9.函數參數 - 指名參數 Named parameters

function func(arg0, {opt1, opt2}) {
    return [opt1, opt2];
}

func(0, {opt1: 'a', opt2: 'b'}) // ['a', 'b']

一樣是經過對象帶來的變化。有個複雜點的例子:

class Entries {
    // ...
    selectEntries({ from = 0, to = this.length } = {}) {
    // Long: { from: from=0, to: to=this.length }

        // Use `from` and `to`
    }
}

let entries = new Entries();
entries.selectEntries({ from: 5, to: 15 });
entries.selectEntries({ from: 5 });
entries.selectEntries({ to: 15 });

指名參數+賦值解構+默認參數,看着反而有點混亂了……自由度大天然帶來閱讀難度的上升,這又是一個權衡點。

10.胖箭頭函數 Arrow functions

let bob = {
    name: "Bob Dylan",

    holdConcert: function (songList) {
        songList.forEach(song => {
            console.log(this.name + " sang " + song)
        });
    }
}

這裏形式上借鑑了CoffeeScript裏「fat arrow」(ES6對執行和內存上有優化)。Arrow functions主要作了兩件事:

  1. 簡化了代碼形式,默認return表達式結果。
  2. 自動綁定語義this,即定義函數時的this。如上面例子中,forEach的匿名函數參數中用到的this

來看幾個例子:

let squares = [ 1, 2, 3 ].map(x => x * x);

x => x + this.y
// 至關於
function(x) { return x + this.y }.bind(this)
// 但胖箭頭在執行效率上會更高

胖箭頭函數與正常函數的區別:

  1. 胖箭頭在建立時即綁定this(lexical this);正常函數的this是在執行時動態傳入的(dynamic this)。
  2. 胖箭頭沒有內部方法[[Construct]]和屬性原型,因此new (() => {})是會報錯的。
  3. 胖箭頭沒有arguments變量。

這樣,之後在定義方法/函數時,就有了清晰的選擇:

  1. 定義子程序(subroutine),用胖箭頭,自動得到語義this。
  2. 定義方法(method),用正常函數,動態this。並且能夠用方法定義特性簡寫代碼,避免function關鍵字出現。

11.字符串模板 Template strings

templateHandler`Hello ${first} ${last}!`

${first}這樣的結構在Ruby的字符串處理很常見,first是動態替換的部分。templateHandler是替換後的處理函數。

固然也能夠不要handler,那就僅僅是模板替換了:

if(x > MAX) {
    throw new Error(`At most ${MAX} allowed: $(x)!`);
}

Template strings支持多行,其間的文本也不會被轉碼:

var str = String.raw`This is a text
with multiple lines.

Escapes are not interpreted,
\n is not a newline.`;

結合不一樣的handler,用法多樣,好比正則:

let str = "Bob Dylan - 2009 - Together Through Life";
let albumInfo = str.match(XRegExp.rx`
    ^(?<artist>[^/]+ ) - (?<year>\d{4}) - (?<albumTitle>[^/]+)$
`);
console.log(albumInfo.year); // 2009

12.迭代器 Iterators

稍微熟悉函數式編程(Python,Ruby也能夠)的朋友對着這個概念應該都不陌生。ES6參考了Python的設計,迭代器有個next方法,調用會返回:

  1. 返回迭代對象的一個元素:{ done: false, value: elem }
  2. 若是已到迭代對象的末端:{done: true[, value: retVal] }

上面第二種狀況中的條件返回部分是爲了遞歸調用生成器而設計的(迭代器實際上是生成器的應用之一),具體說明參見這篇文章的對應部分

下例實現了一個數組的迭代器:

function createArrayIterator(arr) {
    let index = 0;
    return {
        next() {
            if (index < arr.length) {
                return { done: false, value: arr[index++]  };
            else {
                return { done: true }
            }
        }
    }
}

let arr = [1,2,3];
let iter = createArrayIterator(arr);
console.log(iter.next());  // 1
console.log(iter.next());  // 2

在ES6中,可迭代數據結構(好比數組)都必須實現一個名爲Symbol.iterator的方法,該方法返回一個該結構元素的迭代器。注意,Symbol.iterator是一個SymbolSymbol是ES6新加入的原始值類型。

針對可迭代的數據結構,ES6還引入了一個新的遍歷方法 for-of。再舉個例子,改造下上例中的createArrayIterator

function createArrayIterator(arr) {
    let index = 0;
    return {
        [Symbol.iterator]() {
            return this; // 由於自己就是個迭代器
        },
        next() {
            ...
        }
    }
}

let arr = [1, 2, 3];
for(x of createArrayIterator(arr)) { // 注意看
    console.log(x);
}

固然,ES6中的數組自己就是可迭代的,上例僅僅是爲了展現而已。

13.生成器 Generators

ES6的生成器一樣借鑑了Python,經過操做符yield掛起繼續

生成器的寫法比較怪異,使用了關鍵字function*

function* generatorFunction() {
    yield 1;
    yield 2;
}

生成器返回一個對象,用來控制生成器執行,這個對象是可迭代的:

let genObj = generatorFunction();
genObj.next(); // { done: false, value: 1 }
genObj.next(); // { done: false, value: 2 }
genObj.next(); // { done: true }

下面這個例子演示了可遞歸調用的生成器,用到了操做符yield*

function* iterTree(tree) {
    if (Array.isArray(tree)) {
        for (let i = 0; i < tree.length; i++) {
            yield* iterTree(tree[i]);  // (*)
        }
    } else {
        yield tree;
    }
}

yield*會交出(yield)所有迭代對象,而不只僅是一個元素值。原話是「yield* in line (*) yields everything that is yielded by the iterable that is its operand. 」

yield*還能夠傳遞返回值。如:

let result1 = yield* step(); // step也是個generator

這個例子不太好,或者說,ES6的這部分實現有點繁瑣,須要更多示例才能理解這個特性。

相關文章
相關標籤/搜索