2016年7月22日 星期五

ES6-基礎篇

Let + Const



// 定義常量
const REG_GET_INPUT = /^\d{1,3}$/;

// 定義配置項
let config = {
  isDev : false,
  pubDir: './admin/'
}

// 引入 gulp
let gulp    = require('gulp');

// 引入gulp相關插件
let concat  = require('gulp-concat');
let uglify  = require('gulp-uglify');
let cssnano = require('gulp-cssnano');


很多人看完概念之後,第一印象都是:「const 是表示不可變的值,而 let 則是用來替換原來的 var 的。」
所以就會出現上面代碼中的樣子;一段代碼中出現大量的 let,只有部分常量用 const 去做定義,這樣的使用方式是錯誤的。

你可能不知道的事

const 的定義是不可重新賦值的值,與不可變的值(immutable value)不同;const 定義的 Object,在定義之後仍可以修改其屬性。
所以其實他的使用場景很廣,包括常量、配置項以及引用的組件、定義的 「大部分」 中間變量等,都應該以const做定義。反之就 let 而言,他的使用場景應該是相對較少的,我們只會在 loop(for,while 循環)及少量必須重定義的變量上用到他。
猜想:就執行效率而言,const 由於不可以重新賦值的特性,所以可以做更多語法靜態分析方面的優化,從而有更高的執行效率。
所以上面代碼中,所有使用 let 的部分,其實都應該是用 const 的。

Template Strings(字符串模板)

字符串模板是我剛接觸ES6時最喜歡的特性之一,他語法簡潔,語義明確,而且很好的解決了之前字符串拼接麻煩的問題。
因為他並不是 「必須」 的,而且原有的字符串拼接思維根深蒂固,導致我們很容易忽視掉他。

使用實例

我們先來看看他的一般使用場景:


const start = 'hi all';

const getName = () => {
  return 'jelly';
};

const conf = {
  fav: 'Coding'
};

// 模板
const msg = `${start}, my name is ${getName()}, ${conf.fav} is my favourite`;

你可能不知道的事



// 1. 與引號混用
const wantToSay = `I'm a "tbfed"`;

// 2. 支持多行文本
const slogan = 
`
I have a dream today!
`;

// 比較適合寫HTML
const resultTpl = 
`
  <section>
    <div>...</div>
  </section>
`;

Enhanced Object Literals(增強的對象字面量)

增強的對象字面量是 ES6 中的昇華功能,他設計了很多簡寫,這些簡寫不但保留了明確的語義,還減少了我們多餘的代碼量。
當他的使用成為一個習慣時,我們會看到自己代碼變得更為優雅。

你可能不知道的事




const _bookNum = 4;

const basicConfig = {
  level: 5
}

const config = {
  // 直接指定原型對象
  __proto__: basicConfig,
  
  // 屬性簡寫
  _bookNum,
  
  // 方法簡寫
  getBookNum() {
    return this.bookNum;
  }
}

Arrows and Lexical This(箭頭函數)

箭頭函數是ES6中的一個新的語法特性,他的用法簡單,形態優雅,備受人們青睞。
大多數同學初識這個特性時,更多的僅僅用它作為函數定義的簡寫,這其實就有些屈才了。


// 未使用箭頭函數的寫法
{
  ...
  
  addOptions: function (options) {
  
    var self = this;
  
    options.forEach(function(name, opts){
      
      self[name] = self.addChild(name, opts);
      
    });
    
  } 
}

// 使用箭頭函數後的寫法
{
  ...
  
  addOptions: function (options) {
  
    options.forEach((name, opts) => {
      
      this[name] = this.addChild(name, opts);
      
    });
    
  } 
}

可以注意到上下兩段代碼的區別。
在未使用箭頭函數前,我們在過程函數中使用父級 this,需要將其顯式緩存到另一個中間變量中,因為過程函數有獨立的 this 變量,會覆蓋父級;使用箭頭函數後,不但簡寫了一個過程函數( forEach 的參數),還省略掉了 this 的中間變量的定義。
原因:箭頭函數沒有獨立執行上下文( this ),所以其內部引用 this 對象會直接訪問父級。
插播:原來我們定義這個中間變量還有一個有趣的現象,就是明明千奇百怪,例如self, that, me, _that, _me, Self...,快站出來說說你用過哪個,還是哪幾個~
當然,從這塊我們也可以看出,箭頭函數是無法替代全部 function 的使用場景的,例如我們需要有獨立 this 的函數。

你可能不知道的事

  1. 箭頭函數不但沒有獨立 this,他也沒有獨立的 arguments,所以如果需要取不定參的時候,要麼使用 function,要麼用 ES6 的另一個新特性 rest(具體在 rest 中會有詳解)。
  2. 箭頭函數語法很靈活,在只有一個參數或者只有一句表達式做方法體時,可以省略相應括號。


// 完整寫法
const getOptions = (name, key) => {
  ...
}

// 省略參數括號
const getOptions = key => {
  ... 
}

// 省略參數和方法體括號
const getOptions = key => console.log(key);

// 無參數或方法體,括號不能省略
const noop = () => {};

有個簡單小栗子,這一靈活的語法在寫連續的Promise鏈式調用時,可以使代碼更加優雅。


gitPromise
  .then(() => git.add())
  .then(() => git.commit())
  .then(() => git.log())
  .then((msg) => {
      ...
  })
  .then(() => git.push())
  .catch((err) => {
      utils.error(err);
  });

Destructuring(解構)

解構這個特性可以簡單解讀為分別定義,用於一次定義多個變量,常常用於分解方法返回對象為多個變量,分別使用。
使用過ES6的同學應該或多或少接觸過這個特性,但是你可能不知道它如下幾個用法:

你可能不知道的事



const bookSet = ['UED', 'TB fed', 'Not find'];
const bookCollection = () => {
  return {book1: 'UED', book2: 'TB fed'};
};

// 1. 解構也可以設置默認值
const {book1, book3 = 'Not find'} = bookCollection();

// 2. 解構數組時候是可以跳過其中某幾項的
const [book1,,book3] = bookSet;  // book1 = 'UED', book3 = 'Not find'

// 3. 解構可以取到指定對象的任何屬性,包括它包含的方法
const {length: setLength} = bookSet;  // setLength = 3

Rest + Spread

Rest 和 Spread 主要是應用 ... 運算符,完成值的聚合和分解。

你可能不知道的事




// 1. rest 得到的是一個真正的數組而不是一個偽數組
const getOptions = function(...args){
  console.log(args.join); // function
};

// 2. rest 可以配合箭頭函數使用,達到取得所有參數的目的
const getOptions = (...args) => {
  console.log(args); // array
};

// 3. spread 可以用於解構時,聚合所得的值
const [opt1, ...opts] = ['one', 'two', 'three', 'four'];

// 4. spread 可以用於數組定義
const opts = ['one', 'two', 'three', 'four'];
const config = ['other', ...opts];

Classes

ES6 中實現的一個語法糖,用於簡化基於原型集成實現類定義的場景。
雖然有很多人不太喜歡這個特性,認為它作為一個簡單增強擴展,並沒有其他語言 class 應有的特點。
但是就我自己觀點來看,還是感覺這樣一種寫法確實比原有的原型繼承的寫法語義更清晰、明確,而且語法更簡單。
同樣,可能有些用法是你之前容易忽略掉的,在此做個補充。

你可能不知道的事




// 1. 靜態變量
// ES6 的類定義實現了靜態方法的定義,但靜態變量呢?
// 可以用如下方式實現: 
class TbFedMembers{
  static get HuaChen(){
    return 'jelly';
  }
}
TbFedMembers.HuaChen; // "化辰"

// 2. 私有屬性(私有屬性有多種實現方式,只談及其中一種)
// 閉包
const TbFedMembers = (() => {
  const HuaChen = 'jelly';
  
  return class{
    getOneMemberName(){
      return HuaChen;
    }
  };
})();

Promises

Promise 不只是一個對象、一個語法,他更是一種異步編程方式的變化
相信使用過 ES6 的同學都已經開始嘗試了 Promise,甚至在不支持ES6的時候,已經開始使用一些基於 Promise 思想的開源框架。
那麼我們之前用 Promise 究竟用的對麼?有什麼需要注意的點呢?

你可能不知道的事





// 1. 多個異步任務同時執行用 Promise.all,順序執行使用鏈式調用
// Promise.all
Promise
  .all([jsBuildPromise, cssBuildPromise])
  .then(() => {
    ...
  });

// chain
jsBuildPromise
  .then(() => cssBuildPromise)
  .then(() => {
    ...
  });


// 2. Promise 的鏈式調用需要每一個過程返回一個 Promise 對象才能保證順序執行
gitPromise
  .then(() => git.add())  // 正確,箭頭函數簡寫
  .then(() => {
    git.commit(); // 錯誤,函數返回 undefined,會立即執行下一過程
  })
  .then(() => {
    return git.log(); // 正確
  });


// 3. Promise 需要調用 catch 方法來捕獲錯誤,而且過程內的錯誤不會阻塞後續代碼執行
new Promise(() => {
  f;  // not define error !
})
.catch((err) => {
  console.log(err)  // show 'f is not define'
});
console.log('error test');  // 此行可以被正常執行

結語

基礎篇主要是講了我們最常用的一些特性,後續如果大家感興趣,還可以再來個 「進階篇」,最後,希望文章中的部分內容可以對大家理解和使用 ES6 有所幫助。

參考資料