ES6 Preview

這篇文章用來記錄ES6的一些新功能。參考Lukehoban的版本並加以中文化。 基本上所有的example codes我都有在ES6 fiddle或用Traceur執行過。

ES6包含這些新的功能:
- let + const
- enhanced object literals
- arrows
- classes
- template strings
- destructuring
- default + rest + spread
- iterators + for..of
- generators
- comprehensions
- unicode
- modules
- module loaders
- map + set + weakmap + weakset
- proxies
- symbols
- promises
- math + number + string + object APIs
- binary and octal literals
- reflect api
- tail calls

ECMAScript 6 Features

Let + Const

let可以想成是新的var,建立的變數只會存活在Block scope裡。 const是用來建立常數。

1
2
3
4
5
6
7
8
9
10
11
12
13
function x() {
  let a = 2;
  a = a + 3;
  return a;
}

console.log(x()); //5
console.log(a); // undefined

let x = 44;
let x = 55; //應該要報錯,但目前模擬器實作都不會報錯
const b = "hello";
b = "abc"; //應該要報錯,但目前模擬器實作都不會報錯

Enhanced Object Literals

增強的物件描述(Object Literal)支援在建構子設定prototype。可直接在定義的函數內呼叫父類別方法(super call)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var foo = "i am foo";

var obj = {
  __proto__: [],

  // 定義方法
  getLength() {
    return this.length;
  },

  // 動態設定變數/方法名稱
  [ 'prop_' + (() => 42)() ]: "Should be 42",

  //foo:foo 的縮寫
  foo
};

obj.push("1");
console.log(obj.getLength()); //1
console.log(obj.prop_42); //"Should be 42"
console.log(obj.foo); //"i am foo"

Arrows

箭號’=>’是一種定義函數的縮寫方式。 同時支援函數表達式(function expression)跟函數聲明(function statement)。 但跟傳統函數有點不一樣的是在箭號定義函數裡面的this是跟呼叫它的程式碼擁有一樣的lexical scope。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let square = x => x * x;
let add = (a, b) => a + b;
let pi = () => 3.1415;

console.log(square(5)); //25
console.log(add(3, 4)); //7
console.log(pi()); //3.1415

// Lexical this
var bob = {
  _name: "Bob",
  _friends: ['Ken'],
  printFriends() {
    this._friends.forEach(f =>
      console.log(this._name + " knows " + f)
    );
  }
}
bob.printFriends(); //Bob knows Ken

Classes

ES6類別提供一種簡單宣告方式讓class pattern更容易使用。 類別支援繼承(inheritance),父類別呼叫(super calls),建構子(constructor),個體方法(instance method)和類別方法(class method)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Polygon {
  constructor(height, width) { //class constructor
    this.name = 'Polygon';
    this.height = height;
    this.width = width;
  }

  static doIt() {  //class method
    console.log('Do it now');
  }
}

class Square extends Polygon {
  constructor(length) {
    super(length, length); //call the parent method with super
    this.name = 'Square';
  }

  get area() { //calculated attribute getter
    return this.height * this.width;
  }
}

let s = new Square(5);
console.log(s.area); //25
Square.doIt(); //"Do it now"

Template Strings

Template String提供許多方便方法建立字串。 還可以使用函式來預先處理Template String。被稱為Tagged Template String。可以避免被塞入非預期中的字串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var myString = `In JavaScript '\n' is a line-feed.`;

// Multiline strings
var myString2 = `In JavaScript this is
 not legal.`;

var name = "Bob", time = "today";
var myString3 = `Hello ${name}, how are you ${time}?`

console.log(myString); //"In JavaScript ' ' is a line-feed."
console.log(myString2); //"In JavaScript this is not legal."
console.log(myString3); //"Hello Bob, how are you today?"

function tag(strings, ...values) {
  if (!(strings[0] === 'a' && strings[1] === 'b')) {
    return 'bad';
  }
  return 'good';
}
console.log(tag `a${ 123 }b`);  // "good"
console.log(tag `c${ 123 }d`);  // "bad"

Destructuring

Destructuring assignment允許使用陣列或物件pattern來自動給予值。 Destructuring是錯誤安全(fail-soft)的。當配對不上時會給予undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let [one, two, three] = [1, ,3];
let {four, five} = {four:4, five:  5};
console.log(two === undefined); //true
console.log(one, two, three, four, five); // 1 3 4 5

function today() { return { d: 11, m: 3, y: 2014 }; }
var { m: month, y: year } = today();
console.log(month); //3
console.log(year); //2014

books = [];
books.push({ title:"hello", author:"samuel" });
books.push({ title:"hello2", author:"samuel2" });
books.forEach(function ({ title: t, author: a }) {
  console.log(t, a);
  // hello samuel
  // hello2 samuel2
})

Default + Rest + Spread

函數參數可設定預設值(Default)

1
2
3
4
5
6
7
8
function f(x, y=12) {
  // y值會是12 當呼叫者沒有傳入值或傳入值為undefined
  return x + y;
}
console.log(f(3)); //15
console.log(f(3, undefined)); //15
console.log(f(3, null)); //3
console.log(f(3, 4)); //7

不定長度函數參數(Rest Parameter),函數傳入值當做陣列處理

1
2
3
4
5
6
function f(x, ...y) {
  // y 是一個陣列
  return x * y.length;
}
console.log(f(3, "hello", true)); //6
console.log(f(3, "hello", true, 444)); //9

Spread Operator可使用陣列當做函數參數,陣列內的值會被當做相對應的參數值

1
2
3
4
5
6
7
8
9
10
function f(x, y, z) {
  return x + y + z;
}
console.log(f(...[1,2,3])); //6
console.log(f(...[1,2,3,4,5])); //6

var x = [1, 2];
var y = [3, 4];
x.push(...y);
console.log(x); //1,2,3,4

Iterators + For..Of

Iterator Object讓使用者可以客製化iteration行為,就像Java的Iteratable。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//For..Of
let arr = [1, 2, 3, 4, 5];
let sum = 0;

for (let v of arr) {
  sum += v;
}

console.log('1 + 2 + 3 + 4 + 5 =', sum); // 1 + 2 + 3 + 4 + 5 = 15

//Basic Iterator
// fibonacci整個object就是iterator
let fibonacci = {
  [Symbol.iterator]() {
    let pre = 0, cur = 1;
    return {
      next() {
        [pre, cur] = [cur, pre + cur]; //每次循徊把pre值設成cur,cur值則為cur+pre
        return { done: pre > 100, value: pre } //當pre值大於100時終止
      }
    }
  }
}

for (var n of fibonacci) {
  console.log(n); // 1 1 2 3 5 8 13 21 34 55 89
}

// Iteration相關的介面
interface IteratorResult {
  done: boolean;
  value: any;
}
interface Iterator {
  next(): IteratorResult;
}
interface Iterable {
  [Symbol.iterator](): Iterator
}

Generators

Generator使用function*來宣告函數並且返回一個Generator實例。 Generator實例是一種Iterator。可以透過yieldnext可以一步一步執行函數內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function* range(start, end, step) {
  while (start < end) {
    yield start;
    start += step;
  }
}

for (let i of range(0, 10, 2)) {
  console.log(i); // 0 2 4 6 8
}


//Fibonacci數列實作by Generator
var fibonacci = function*() {
  var pre = 0, cur = 1;
  while (true) {
    yield cur;
    var tmp = cur;
    cur += pre;
    pre = tmp;
  }
}

f = fibonacci();
for (var n of f) {
  if (n > 100)
    break;
  console.log(n); // 1 1 2 3 5 8 13 21 34 55 89
}

//你也可以自己使用next一步一步取值
//可以用done這個boolean值來判斷是否已經沒有next
f = fibonacci();
var obj;
while (!(obj = f.next()).done) {
  if (obj.value > 100) {
    break;
  }
  console.log(obj.value);  // 1 1 2 3 5 8 13 21 34 55 89
}

// Generator的介面
interface Generator extends Iterator {
  next(value?: any): IteratorResult;
  throw(exception: any);
}

Comprehensions

Array Comprehension和Generator Comprehension提供簡單方式來處理數列

1
2
3
4
5
6
7
8
9
10
11
12
//Array Comprehension
let arr = [1, 2, 3, 4, 5];
let squared = [for (x of arr) x * x];

console.log(squared); //1,4,9,16,25

//Generator Comprehension
let squared2 = (for (x of arr) x * x);

for (let z of squared2) {
  console.log(z); //1 4 9 16 25
}

Unicode

完整21bit的Unicode支援

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// same as ES5.1
console.log("𠮷".length == 2); //true

// new RegExp behaviour, opt-in ‘u’
//console.log("𠮷".match(/./u)[0].length == 2); //在ES6模擬器上無法使用

// new form
console.log("\uD842\uDFB7" == "𠮷"); //true
//console.log("\u{20BB7}" == "𠮷"); //在ES6模擬器上無法使用

// new String ops
console.log("𠮷".codePointAt(0) == 0x20BB7); //true

//在ES6模擬器上無法使用
// for-of iterates code points
//for(var c of "𠮷") {
//  console.log(c);
//}

Modules

像AMD,CommonJS一樣可以自行定義module以及彼此之間的相依性。

1
2
3
4
5
6
7
// lib/math.js
export function sum(x, y) {
  return x + y;
}
export var pi = 3.141593;
var msg2 = "Ya";
export { msg2 as message};

使用module語法載入math.js

1
2
3
4
5
// app.js
module math from "lib/math";
console.log("2π = " + math.sum(math.pi, math.pi)); //2π = 6.283186 
console.log(math.msg2); //undefined
console.log(math.message); //Ya

使用import語法來載入sum函數和pi變數

1
2
3
4
// otherApp.js
import {sum, pi} from "lib/math";
console.log("2π = " + sum(pi, pi)); //2π = 6.283186
console.log(message); //undefined 

Module Loaders

Module Loader提供動態載入(Dynamic loading),命名空間(Namespace),狀態獨立(State isolation)等功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//更改預設js目錄
System.baseURL = '/lib/';

//動態載入math.js
System.import('math').then(function(m) {
  //m 是math的namespace
  console.log("2π = " + m.sum(m.pi, m.pi)); //2π = 6.283186
});

//理論上可以用Loader來執行程式碼在某一個context底下
//但模擬器找不到Loader..
// Create execution sandboxes – new Loaders
var loader = new Loader({
  global: fixup(window) // replace ‘console.log’
});
loader.eval("console.log('hello world!');");

Map+Set+Weakmap+Weakset

新的資料結構,由於目前模擬器都還沒有實作所以沒有親自試過。不過我相信這部份上手很快不難。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;

// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;

// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined

// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set

Proxies

Proxy故名思義就是讓對某一個物件(host object)的所有行為透過代理物件(proxy object)。 方便用來logging或效能分析(profiling)。目前模擬器還沒實作。

1
2
3
4
5
6
7
8
9
10
// Proxying a normal object
var target = {};
var handler = {
  get: function (receiver, name) {
    return `Hello, ${name}!`;
  }
};

var p = new Proxy(target, handler);
p.world === 'Hello, world!';
1
2
3
4
5
6
7
8
9
10
// Proxying a function object
var target = function () { return 'I am the target'; };
var handler = {
  apply: function (receiver, ...args) {
    return 'I am the proxy';
  }
};

var p = new Proxy(target, handler);
p() === 'I am the proxy';

Symbols

Symbol是一種新的primitive type。Symbol可以用來當做property的key值而且是唯一的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var key = Symbol("key");

function MyClass(privateData) {
  this[key] = privateData;
}

MyClass.prototype = {
  say: function() {
    console.log(this[key]);
  }
};

var c = new MyClass("hello")
console.log(c["key"]); //undefined 因為Symbol並不是String
console.log(c[key]); //hello
console.log(c.say()); //hello

Promises

Promise就不用再多提,目前已經有很多現成的ES5 library有實作Promise pattern了。 像RSVP.jsQ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…

  if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke"));
  }
});

promise.then(function(result) {
  console.log(result); // "Stuff worked!"
}, function(err) {
  console.log(err); // Error: "It broke"
});

Math+Number+String+Object APIs

ES6增加了許多好用的函數。底下範例在目前模擬器上尚無法完全支援。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false

Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2

"abcde".contains("cd") // true
"abc".repeat(3) // "abcabcabc"

Array.from(document.querySelectorAll('*')) // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
[0, 0, 0].fill(7, 1) // [0,7,7]
[1,2,3].findIndex(x => x == 2) // 1
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"

//Object copying
Object.assign(Point, { origin: new Point(0,0) })

Binary and Octal Literals

新的二進位(b)和八進位(o)表示式

1
2
console.log(0b111110111 === 503); // true
console.log(0o767 === 503); // true

Reflect API

參考ECMAScript wiki

Tail Calls

Tail Call最佳化讓遞迴程式call stack不會無限增加導致記憶體用完。

1
2
3
4
5
6
7
8
9
function factorial(n, acc = 1) {
    'use strict';
    if (n <= 1) return acc;
    return factorial(n - 1, n * acc);
}

// 在目前的browser幾乎都會stack overflow
// 未來ES6上面是安全的
factorial(100000);

Comments