ES6 提供不少語法關於變數宣告與建立

變數宣告部分,除了 var 以外,在 ES6 增加了 letconst,在變數建立部分,則增加了 destructuring 與 spread,當然 TypeScript 也完全支援。

Version


TypeScript 2.3

var


1
2
3
4
5
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1);
}

var 最經典的範例莫過於此,原本我們預期會輸出

1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9

結果輸出

1
2
3
4
5
6
7
8
9
10
10
10
10
10
10
10
10
10
10
10

這裡有兩個觀念:

  • setTimeout() 屬於非同步 function,JavaScript 的 event loop model 會在最後執行。
  • var 看似在 {} 內,但沒有 scope 概念,因為其 hoisting 機制,會將 var 提升到程式最前面,相當於全域變數,因此 console.log() 印出 for loop 執行完的 10

let


1
2
3
4
5
for(let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1);
}

var 改成 let 之後,結果就如預期了。

1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9

let 對變數有 scope 概念,其生命週期只存在於 {} ,因此儘管非同步的 setTimeout() 最後執行,let 仍將 i 鎖住到最後,而不像 var 沒有 scope 概念,都是全域變數。

實務上應該全面使用 let 取代 var,若你使用 var,language service 也會提出警告,要你改用 let。

const


1
const numLivesForCat = 9;

若此變數不會再被修改,則應宣告成 const。

傳統我們都是確定此變數不能被修改,才會宣告成 const,但在 TypeScript 中是反過來,變數應該盡量宣告成 const,除非要修改才宣告成 let,事實上,若 language service 發現變數從來沒被修改過,會主動提出警告,要求你改成 const,這是希望大家盡量寫出 immutable 的 pure function,減少不必要的 side effect。

Destructuring


Array Destructing

ES6 允許我們直接將陣列加以解構成變數。

1
2
3
4
5
var input = [1, 2];
var first = input[0];
var second = input[1];
console.log(first); // outputs 1
console.log(second); // outputs 2

在 ES5,我們必須使用 array index 方式,才能將值指定到變數。

1
2
3
4
let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2

ES6 我們可以直接使用 [] 將陣列加以解構。

1
2
// swap variables
[first, second] = [second, first];

交換變數,只要一行就可以寫出來。

1
2
3
4
5
function f([first, second]: number[]) {
console.log(first); // 1
console.log(second); // 2
}
f([1, 2]);

若用在 function 的參數,可以接陣列解構成變數。

1
2
3
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]

配合 ,將剩餘的值結構成陣列。

first 為單一變數,rest 為陣列。

1
2
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1

first1,其他值會忽略。

1
2
3
let [, second, , fourth] = [1, 2, 3, 4];
console.log(second); // 2
console.log(fourth); // 4

只有 24 會解構,其他值會忽略。

Object Destructing

除了解構陣列外,ES6 還允許我們解構物件。

1
2
3
4
5
6
7
8
9
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;

console.log(a); // foo
console.log(b); // 12

c 因為沒有變數解構,會自動忽略。

1
2
3
4
5
6
7
8
9
10
11
let o = {
a: "foo",
b: 12,
c: "bar"
};

let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length

console.log(a); // foo
console.log(total); // 15

配合 ,將剩餘的值結構成物件。

a 為單一變數,passthrough 為物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
type C = { a: string, b: number };

function f({a, b}: C): void {
console.log(a); // foo
console.log(b); // 12
}

let o = {
a: "foo",
b: 12
};

f(o);

若用在 function 的參數,可以接物件解構成變數。

Spread


1
2
3
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];

會將陣列加以展開。

bothPlus[0, 1, 2, 3, 4, 5]

1
2
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };

會將物件屬性加以展開。

search{ food: "rich", price: "$$", ambiance: "noisy" }

若屬性有重複,將後面蓋前面。

1
2
3
4
5
6
7
8
9
class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!

只能展開 property,不能展開 method。

Conclusion


  • 實務上應該盡量使用 const,除非變數需要被修改才用 let
  • 盡量寫出 pure function 減少 side effect。
  • Destructuring 與 spread 非常好用,可以讓程式碼更精簡。

Reference


TypeScript, Handbook : Variable Declaration

2017-06-16