ES6 in Depth 閱讀筆記 Part 1 - loop, generator, template string, rest arguments, default parameters
2017-04-03 19:48:13

前言

這是ES6 In Depth的閱讀筆記,只記錄程式的範例方便語法的查詢,但我強列推薦去讀讀這系列的原始文章,它對於ES6的語法有很深入的介紹,非常值得一讀。

Iterators and the for-of loop

for

for (var index = 0; index < myArray.length; index++) {
  console.log(myArray[index]);
}

缺點:要先知道array的大小。

forEach

myArray.forEach(function (value) {
  console.log(value);
});

缺點:無法使用break與return。

for in

for (var index in myArray) {    // don't actually do this
  console.log(myArray[index]);
}

缺點:1. index的值不是integer,而是string。2. for in不只會跑過array裡的element,連動態加入屬性(expando properties)的值也會一起跑。3. 在某些情況下,for in不會照array的順序,而是照element的文字排序進行迴圈。簡言之,for in當初只是為了object而設計的,不適合用在array中。

for of

for (var value of myArray) {
  console.log(value);
}

for of 基本上解決了上面迴圈遇到的問題,帶來了下面的優點:

  • 與 forEach / for in 一樣,採用iterator的方式進行迴圈。
  • 可以使用 break, return, continue。
  • 沒有 for in 的問題。
  • 支援string與任何類array資料的迴圈,例如 Map 與 Set。
for (var [key, value] of phoneBookMap) {
  console.log(key + "'s phone number is: " + value);
}
  • 不支援object的迴圈(可以使用for in來替代),但可以用 Object.keys 的方式來取得對應object的key。
for (var key of Object.keys(someObject)) {
  console.log(key + ": " + someObject[key]);
}
  • 支援任何實作iterable的物件。
var zeroesForeverIterator = {
  [Symbol.iterator]: function () {
    return this;
  },
  next: function () {
    return {done: false, value: 0};
  }
};

Generators

Generator function即是具有暫停功能的function。

function* quips(name) {
  yield "hello " + name + "!";
  yield "i hope you are enjoying the blog posts";
  if (name.startsWith("X")) {
    yield "it's cool how your name starts with X, " + name;
  }
  yield "see you later!";
}

其中要使用 function* 宣告generator function,而 yield 就是類似 return,也就是每次呼叫 next 暫停回傳的值。

> var iter = quips("jorendorff");
  [object Generator]
> iter.next()
  { value: "hello jorendorff!", done: false }
> iter.next()
  { value: "i hope you are enjoying the blog posts", done: false }
> iter.next()
  { value: "see you later!", done: false }
> iter.next()
  { value: undefined, done: true }

Generators are iterators

範例:實作一個function傳入start與end,回傳一個iterator。

class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    } else {
      return {done: true, value: undefined};
    }
  }
}

function range(start, stop) {
  return new RangeIterator(start, stop);
}

上面的程式碼可以用generator簡化成:

function* range(start, stop) {
  for (var i = start; i < stop; i++)
    yield i;
}

常用的情境:

  • 幫物件實作iterable,next的function可以用generator funcation來實作。
  • 簡化使用function建立物件的程式碼。
  • 處理未知大小的資料。
  • 處理複雜或是多層的回圈,generator function可以先把部分的資料吐回來先處理。
  • 處理iterable的物件。

Generators and asynchronous code

Generator的另一個用處是將async的function,轉成用一般function(也就是sync)的方式撰寫,但實際上它仍是async的執行方式。下面是範例:

// Synchronous code to make some noise.
function makeNoise() {
  shake();
  rattle();
  roll();
}

// Asynchronous code to make some noise.
// Returns a Promise object that becomes resolved
// when we're done making noise.
function makeNoise_async() {
  return Q.async(function* () {
    yield shake_async();
    yield rattle_async();
    yield roll_async();
  });
}

Template strings

template string即是可以在字串中帶入變數顯示,這類的字串會用`這個符號來定義字串,如下所示:

`User ${user.name} is not authorized to do ${action}.`

它有下面的特性:

  • 字串裡除了可以放變數外,還可以放任何的表示式。
  • 如果變數是一個物件,而字串會呼叫.toString()這個method。
  • 如果要顯示`,則需要用\做跳脫字元。例如:`\``
  • 如果要顯示${,則同樣也要做跳脫字元,可以選其中一個做即可,例如:\${或是\$\{
  • 支援多行字串,所有的空白類的字元(空白、換行、縮排等)都會包含在字串中。
$("#warning").html(`
  Watch out!
  Unauthorized hockeying can result in penalties
  of up to ${maxPenalty} minutes.
`);
  • template string 不會自動作危險字元的過濾與跳脫,需要特別注意程式碼注入的問題。下面是一個程式範例來處理危險字元的過濾:
var message = SaferHTML`${bonk.sender} has sent you a bonk.`;

function SaferHTML(templateData) {
  var s = templateData[0];
  for (var i = 1; i < arguments.length; i++) {
    var arg = String(arguments[i]);
    s += arg.replace(/&/g, "&")
            .replace(//g, ">");
    s += templateData[i];
  }
  return s;
}
  • 如果要處理 template string 的 i18n,需要在變數的地方自行做處理,例如下面的例子就是加上額外tag的方式來辨識需要翻譯的地方:
i18n`Hello ${name}, you have ${amount}:c(CAD) in your bank account.`
// => Hallo Bob, Sie haben 1.234,56 $CA auf Ihrem Bankkonto.
  • template string 並沒有提供內建的迴圈與判斷式,如果要做到這些還是必須使用其它的 template library 例如 Mustache 或是 Nunjuck 。
  • 如果要處理 markdown 的話,需要使用``來呈現程式碼,例如:
To display a message, write ``alert(`hello world!`)``.

Rest parameters and defaults

Rest parameters

如果要在函式中處理不定個數參數的傳入,在ES6之前的作法是使用arguments這個內建變數,範例如下:

containsAll("banana", "b", "nan")

function containsAll(haystack) {
  for (var i = 1; i < arguments.length; i++) {
    var needle = arguments[i];
    if (haystack.indexOf(needle) === -1) {
      return false;
    }
  }
  return true;
}

其中 haystack 的值即為 "banana",而 arguments 的值為 ["banana", "b", "nan"]。這樣的處理方式有幾個缺點:

  • arguments 代表的是所有傳入的參數,無法區分是哪些參數是額外傳進來的。
  • arguments 包含了 haystack 的值,有重複的參數值變成反而有點多餘。

ES6有了 rest arguements 可以改寫成下面這個樣子:

function containsAll(haystack, ...needles) {
  for (var needle of needles) {
    if (haystack.indexOf(needle) === -1) {
      return false;
    }
  }
  return true;
}

其中 haystack 的值即為 "banana",而 needles 的值為 ["b", "nan"]。使用 rest arguments 要注意的是:

  • 只有最後一個參數可以設成 rest arguments。
  • 如果沒有額外參數傳入, rest arguments 為空 array 。

Default parameters

在ES6中允許函式的參數列可以設定預設值,如下所示:

function animalSentence(animals2="tigers", animals3="bears") {
  return `Lions and ${animals2} and ${animals3}! Oh my!`;
}

當參數沒有傳入時,就會使用設定的預設值當做傳入的參數。使用 default parameters 要注意的是:

  • 預設值是即時運算的,你甚至可以在第二個參數的預設值中使用第一個參數的值,例如:
function animalSentenceFancy(animals2="tigers", animals3=(animals2 == "bears") ? "sealions" : "bears") {
  return `Lions and ${animals2} and ${animals3}! Oh my!`;
}
  • 傳入 undefined 的值會視作不傳入參數,如果對應的參數有設定預設值,就會使用預設值。
  • 可以部分的參數設定預設值,部分不設,不設的參數相當於有 undefined 這個預設值。例如function myFunc(a=42, b) {...}相當於function myFunc(a=42, b=undefined) {...}