TypeScript-VariableDecarations

Variable Decarations

letconst 是兩種 Javascript 新的宣告方式, letvar 比較類似

const 則是定義之後禁止之後修改(常數)

TypeScript 也有提供 letconst 的宣告方式,下個部分將會解釋為什麼會推薦使用 letconst

var 宣告

在 Javascript 中宣告一個變數常用的方式是

1
var a = 10;

在上面的例子之中,你宣告了一個變數 a10

你也可以再 Function 中宣告

1
2
3
4
function f(){
var message = 'Hello, World!';
return message
}

也可以允許相同的變數在不同的 Function scope

1
2
3
4
5
6
7
function f(){
var a = 10;
return function g(){
var b = a + 1;
return b;
}
}

在上方的範例中 g() 中可以取得變數 a 得值

1
2
3
4
5
6
7
8
9
10
11
12
function f(){
var a = 1;
a = 2;
var b = g();
b = 3;
return b;
function g(){
return a;
}
}

f(); // result 2

使用 var 宣告會有一些區域的規則問題

1
2
3
4
5
6
7
8
9
function f(shouldInitialize: boolean){
if(shouldInitialize){
var x = 10;
}
return x;
}

f(true); // 10
f(false); //undefined

因為 var 是在 if裡面,所以當 shouldInitialize 是 false 的話就部會執行 if裡面的程式碼

所以 x 並未宣告過,就會造成 undefined

這個規則可能會造成壹些不同種類型的錯誤,其中一種就是當你重複宣告同樣名稱的變數的時候彼此會互相覆蓋

1
2
3
4
5
6
7
8
9
10
11
function sumMatrix(matrix: number[][]){
var sum = 0;
for(var i = 0; i < currentRow.length; i++){
var currentRow = matrix[i];
for(var i =0;i < currentRow.length; i++>){
sum += currentRow[i];
}
}

return sum;
}

上述範例就可以發現因為 i 變數在雙迴圈中會被互相覆蓋造成程式執行上的錯誤,不會依據我們預想的去執行

奇怪的問題

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

但是結過卻是

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

但是我們希望的是

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

因為每次呼叫 setTimeout 會延遲一段時間後才開始執行 Function但是迴圈會不斷覆蓋掉 i 這個變數,而在延遲時間之後呼叫到的 i 則是最後覆蓋成 10 的 i

最常見要解決這件事情的方式如下

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

這個看起來有點奇怪的解決方式在 javascript 中卻是常見解決這個問題的方式

let

現在你已經知道 var 會有一些問題,所以會有一些問題,所以為什麼需要介紹 let.letvar 的使用方式依樣

1
let hello = 'hello';

Block-scoping

使用 let 宣告的時候,它的作用域市 blocking-scope。和 var 宣告的作用域不一樣,他是用大括號來做區隔

1
2
3
4
5
6
7
8
function f(input: boolean){
let a = 100;
if(input){
let b = a + 1;
return b;
}
return b; // b 並不存在
}

上述範例中有 ab 兩個變數, a 的變數範圍在整個 f() Function 之中,而 b 只會存在 if之中

而變數使用 try catch 宣告的範例如下

1
2
3
4
5
6
7
try{
throw 'oh no!';
}catch(e){
console.log('oh well');
}

console.log(e);// e not found

另外一個很重要的 blocking-scope 變數不能在宣告之前做任何動作

1
2
a++;
let a;

TypeScript 中對這樣的提前宣告較為寬鬆,你需要使用 try catch 來取得錯誤訊息

若是沒有使用 try catch TypeScrtip 並不會顯示這個訊息,若是在 ES2015 則會顯示這個錯誤訊息

1
2
3
4
5
6
7
function foo(){
return a;
}

foo(); //會丟出一個錯誤訊息

let a;

重複宣告和 shadowing

若是使用 var 的方式來宣告的話,他不會在意你宣告過幾次

1
2
3
4
5
6
7
function f(x){
var x;
var x;
if(true){
var x;
}
}

使用 let 宣告在同一個 scope 中只能宣告一次

1
2
let x = 10;
let x = 20; // Error
1
2
3
4
5
6
7
8
function f(x){
let x = 100;// Error
}

function g(){
let x = 100;
let x = 120;// Error
}

只要是在不同的 blocking-scope 就可以做同名的宣告

1
2
3
4
5
6
7
8
9
10
11
function (condition, x){
if(condition){
let x = 100;
return x;
}
return x;
}


f(false, 0); // returns '0'
f(true, 0); // returns '100'

宣告一個新的名稱在另外一個內嵌的 block-scoping 這個行為叫做 shadowing,但是這樣的行為會造成一些 bugs

例如:

1
2
3
4
5
6
7
8
9
10
11
function sumMatrix(matrix: number[][]){
let sum = 0;
for (let i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (let i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}

return sum;
}

shadowing 在攥寫程式碼的時候應該要避免的狀況之一

Block-scoped variable capturing

當我們在一個作用域中宣告一個變數與 Function ,而 Function 也是其中一個作用域,在這個 Function 使用已宣告的變數的時候,即使脫離了那個作用域,也是依舊可以使用該變數

1
2
3
4
5
6
7
8
9
10
11
function theCityThatAlwaysSleeps(){
let getCity;
if(true){
let city = 'Seattle';
getCity = function (){
return city;
}
}

return getCity(); // Seattle
}

因為 city 雖然是在 if 的作用域宣告的,但是可以透過 Function 記住他的指標即使脫離作用域之後也可以透過該 Function 做呼叫使用

回憶之前 setTimeout 的範例, let 有相當大程度的不同

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

結果為

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

const

const 是另外一種不同的宣告

1
const numLivesForCat = 9;

雖然看起來跟 let 宣告一樣,雖然他們有相同的 block-scoping 規則,但是還是有些不同

const 宣告的變數是 immutable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const numLivesForCat = 9;
const kitty = {
name: "Aurora",
numLives: numLivesForCat
};

kitty = {
name: 'Danielle',
numLives: numLivesForCat
}; // Error

// All OK
kitty.name = 'Rory';
kitty.name = 'Kitty';
kitty.name = 'Cat';
kitty.numLives--;

除非你要整個複寫整個物件,否則還是可以修改參數值得,

也就是此物件性質為 唯讀 的,詳情參閱

let vs. const

為什麼需要兩個不同的語意卻擁有相同的 block-scoping 的宣告方式呢?

基於 最小權限原則
若之後變數都不需要修改或是物件僅僅提供修改參數的權限時,則使用 const,換句話說若是變數之後有可能會被覆寫則使用 let 來宣告

Destructuring

ES2015 的特性在 Typescript 中依舊可以使用

Array destructuring

1
2
3
4
5
let input = [1, 2];
let [first, secode] = input;

console.log(first); // 1
console.log(secode); // 2

這個解構也可以在 Function 中使用

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

f([1, 2]);

也可以將大量的變數指定給某一個變數

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

當然你也可抵應某些參數

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

也可以單存指定某些特定的參數

1
let [, second, , fourth ] = [1, 2, 3, 4];

Object destructuring

你也可以解構 object

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

let = {a, b} = o;

你可以單純指定 a, bc 可以因為不使用而跳過

你也可以使用 ... 這個形態來指定變數

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

property renaming

你可以對變數重新命名

1
let {a: newName1, b: newName2} = o;

也可以將 a:newName1 改為 a as newName1 也是一樣的效果

TypeScript 也是需要宣告類型

1
let {a, b}: {a: string, b: number} = o;

Default vaules

在你解構的時候也可以提供預設值

1
2
3
function keepWholeObject({a: string, b?: number}){
let {a, b = 1001} = wholeObject;
}

keepWholeObject 中的 b 若是有參數則指定該參數,若是 undefined 或是 null 則指定為預設值 1001

宣告 Function

解構依舊可以使用在宣告函式之中

1
2
3
4
type C = {a: string, b?:number};
function({a, b}: C){
// ...
}

也可以解構時預先放入預設值

1
2
3
4
5
function f({a, b} = {a: "", b: 0}): void{
//...
}

f(); // ok, default to {a: "", b: 0}

參數與設定型態都可以給予預設值,但是這兩個又有什麼不同呢?

1
2
3
4
5
6
function({a, b=0}: {a: ""}): void{
//...
}
f({a: 'yes'}); // ok default b = 0
f(); // default to a = {a: ""}, default b = 0;
f({}); // error 'a' is required

Spread

ES2015 中的 Spread 特性也是支援的

1
2
3
4
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
console.log(bothPlus); // [0, 1, 2, 3, 4, 5]

Spread 也可以使用在 object

1
2
3
let defaults = {food: 'spicy', price: '$$', ambiance: 'noisy'};
let search = {...defaults, food: 'rich'};
console.log(search); // {food: 'rich', price: '$$', ambiance: 'noisy'}

在上述範例中 search 會解構 defaults 而且後面的 food 因為和 defaults 中重複而且順序在 defaults 的後面,所以會被覆蓋

1
2
3
let defaults = {food: 'spicy', price: '$$', ambiance: 'noisy'};
let search = {food: 'rich', ...defaults};
console.log(search); // {food: 'spicy', price: '$$', ambiance: 'noisy'}

但是 Spread 只會繼承特性,部會繼承 Function

1
2
3
4
5
6
7
8
9
10
11
12
13
class C{
p = 12;
m(){
//...
}
}

let c = new C();

let clone = {...c};

console.log(clone.p); // 12
console.log(clone.m); // undefined

另外 TypeScript 編譯過程並不允許 generator functionSpread 參數傳遞

文章目录
  1. 1. Variable Decarations
    1. 1.1. var 宣告
      1. 1.1.1. 奇怪的問題
    2. 1.2. let
      1. 1.2.1. Block-scoping
      2. 1.2.2. 重複宣告和 shadowing
      3. 1.2.3. Block-scoped variable capturing
    3. 1.3. const
    4. 1.4. let vs. const
    5. 1.5. Destructuring
      1. 1.5.1. Array destructuring
      2. 1.5.2. Object destructuring
      3. 1.5.3. property renaming
      4. 1.5.4. Default vaules
      5. 1.5.5. 宣告 Function
    6. 1.6. Spread
|