跳轉到

2015:ECMAScript 6(ES2015)——現代化的黎明#

事件#

2015年6月,ECMAScript 2015(ES6) 發佈——自 ES5(2009)六年後的最大升級,也是 JavaScript 歷史上規模最大的一次改版。從此標準改為每年發佈一次,並以年份命名。

背景:為何需要 ES6#

  • Node.js(2009)讓 JS 跑大型伺服器程式,需要模組、類別、區塊作用域。
  • 回呼地獄(callback hell)讓非同步程式碼不可維護,需要 Promise。
  • var 的函數作用域與提升(hoisting)是 bug 溫床。
  • 框架(Angular、React)大量重複實作 class、模組、模板——這些應該在語言層面解決。

新語法與特性#

1. let / const:塊級作用域#

  • 為何加入:var 是函數作用域且會提升,經典 bug:迴圈閉包陷阱、變數在宣告前可用。
  • 理論原因:回到 Scheme 的詞法作用域正統——作用域跟著 {} 走。
  • 實用原因:一次宣告錯誤就能讓數百行程式碼出錯。
  • 彌補缺陷:var 的 hoisting 與作用域洩漏。

2. 箭頭函數(=>)#

  • 為何加入:ES5 需要寫 function(){} 且 this 會丟失(要 var self = this 或 .bind())。
  • 理論原因:λ 演算的簡潔語法;箭頭函數詞法綁定 this(取決外層作用域)。
  • 實用原因:回呼成為主流(map/filter、Promise.then),冗長的 function 關鍵字與 this 陷阱天天折磨開發者。
  • 彌補缺陷:回呼的 this 丟失與樣板冗長。

3. Promise#

  • 為何加入:回呼地獄(巢狀回呼、錯誤要層層傳遞)。
  • 理論原因:源自1976年 Promise 理論(Friedman & Wise)與1977年的 futures;Compliance 與 CommonJS Promise/A+ 社群規範先於語言標準。
  • 實用原因:Ajax 時代所有 I/O 都是非同步,回呼版本無法組合、無法統一錯誤處理。
  • 彌補缺陷:回呼地獄與非同步錯誤處理。

4. class(類別)#

  • 為何加入:ES5 的原型繼承要寫 Child.prototype = Object.create(Parent.prototype),且大多數開發者根本寫不對。
  • 理論原因:妥協之作——語法糖包裝原型繼承(本質仍是 prototype),並非新增繼承模型。Eich 保留了 Self 的核心,只是加上 SIMULA(1967)式的語法外殼。
  • 實用原因:Java/C# 開發者大量湧入(Node 時代),class 語法降低遷移成本。
  • 彌補缺陷:原型繼承的可用性。

5. 模組(import / export,ESM)#

  • 為何加入:ES 完全沒有模組系統,社群自創 CommonJS(Node)與 AMD(RequireJS),兩者不相容。
  • 理論原因:靜態結構——ESM 的 import/export 可在編譯期分析(這是 tree-shaking、迴圈偵測的基礎,CommonJS 做不到)。
  • 實用原因:打包工具需要靜態分析;瀏覽器需要無 node_modules 的標準模組。
  • 彌補缺陷:模組戰國與動態相依的不可分析性。

6. 模板字面量(`)#

  • 為何加入:ES5 字串拼接用 +,多行要 \n,易錯且醜。
  • 實用原因:DOM 模板生成是最大場景;內嵌變數與多行一次解決。
  • 彌補缺陷:字串拼接的樣板與錯誤。

7. 解構賦值與展開運算子(...)#

  • 理論原因:源自 LISP 的 car/cdr 解構與 ML/Erlang 的模式匹配傳統。
  • 實用原因:函數多回傳值、物件取欄位的樣板程式碼。
  • 彌補缺陷:var a = obj.a; var b = obj.b; 的樣板。

8. 迭代器與生成器(for...of、function*、yield)#

  • 理論原因:Python/CLU 的迭代器協議、Scheme 的 continuation;yield 實現可暫停函數。
  • 實用原因:統一遍歷協議(陣列、Map、Set、字串、自訂物件);生成器後來成為 Babel 實作 async/await 的降級目標。
  • 彌補缺陷:for-in 遍歷物件屬性的混亂與無統一協議。

9. 其他#

  • Map / Set(真正的雜湊表——以前用物件當雜湊表有原型污染與僅字串鍵的缺陷)、Object.assign、預設參數、其餘參數、Symbol、二進位/八進位字面量、Number.isInteger 等。

理論總結#

ES6 是「借鑑各家之長」的大融合:let/const 回歸 Scheme 詞法作用域、class 借 SIMULA 語法、Promise 借 1976 理論、解構借 ML 模式匹配、模組借靜態編譯理論。它沒有發明新概念,而是把三十年來程式語言界的成熟理論補進 JavaScript。

實用總結#

ES6 的落地依賴 Babel 轉譯(2014,見 2014-Babel與webpack轉譯時代)——「社群先試、標準後收」的新模式由此確立。ES6 也標誌著 TC39 轉向年度發佈、開放提案流程的民主化。

相關條目#

程式範例#

1. let/const:塊級作用域消滅 hoisting bug#

// ES5:經典閉包陷阱——印出 3, 3, 3
for (var i = 0; i < 3; i++) {
  setTimeout(function () { console.log(i); }, 0);
}

// ES6:let 是塊級作用域——印出 0, 1, 2
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}

// const:重新賦值報錯
const PI = 3.14;
PI = 3;   // TypeError: Assignment to constant variable

2. 箭頭函數:this 詞法綁定消滅回呼陷阱#

// ES5:回呼中 this 丟失
var timer = {
  count: 0,
  start: function () {
    setInterval(function () { this.count++; }, 1000);  // this = window,壞了
  }
};

// ES6:箭頭函數的 this 取自外層
var timer = {
  count: 0,
  start: function () {
    setInterval(() => { this.count++; }, 1000);        // this = timer,正確
  }
};

3. Promise:回呼地獄的解方#

// ES5:回呼地獄(見 2009 Node 範例)
// ES6:Promise 鏈——可組合、統一錯誤處理
fetch("/api/config")
  .then((res) => res.json())
  .then((cfg) => fetch(cfg.url))
  .then((res) => res.json())
  .then((data) => console.log(data))
  .catch((err) => console.error(err));   // 任何一步失敗都到這裡

4. class:原型繼承的語法糖#

class Point {
  constructor(x, y) { this.x = x; this.y = y; }
  sum() { return this.x + this.y; }
}
class Point3D extends Point {              // 繼承:一行,不用碰 prototype
  constructor(x, y, z) {
    super(x, y);                           // 呼叫父類建構
    this.z = z;
  }
}
new Point3D(1, 2, 3).sum(); // 3

5. 模板字面量、解構、其餘/展開#

const name = "Ada";
const msg = `Hello, ${name}!
多行字串一次搞定`;                          // 模板字面量

const { name: n, age = 0 } = user;          // 解構 + 預設值
const [first, ...rest] = [1, 2, 3];         // 陣列解構 + 其餘
const merged = { ...defaults, ...options }; // 物件展開

6. 迭代器/生成器與 Map/Set#

function* fib() {                            // 生成器:可暫停函數
  let a = 0, b = 1;
  while (true) { yield a; [a, b] = [b, a + b]; }
}
for (const v of fib()) { if (v > 100) break; console.log(v); }

const map = new Map();                       // 真雜湊表:任意鍵、無原型污染
map.set("x", 1).set(42, "any key");          // 物件當雜湊表做不到
const set = new Set([1, 1, 2]);              // set.size === 2