iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 2
1

變數

每一種程式語言的第一步都是從宣告變數開始

但是 TypeScript 其實最後編譯結果還是 Javascript

我們還是以 Javascript 的宣告變數基礎來談談

而類型控制之後再說吧

宣告變數

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

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

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

Var 宣告

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

var a = 10;

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

你也可以再 Function 中宣告

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

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

function f(){
  var a = 10;
  return function g(){
    var b = a + 1;
    return b;
  }
}

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

function f(){
  var a = 1;
  a = 2;
  var b = g();
  b =  3;
  return b;
  function g(){
    return a;
  }
}

f(); // result 2

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

function f(shouldInitialize: boolean){
  if(shouldInitialize){
    var x = 10;
  }
  return x;
}

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

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

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

Hoisting

而結果是 undefined 的原因是 Javascript 的特性

當 Javascript 執行程式之前會先將所有使用的變數宣告為 undefined

來佔住記憶體空間

console.log(f); // undefined

f = function(a){
console.log(a);
}
console.log(f); // functioin  f

console.log(a); // undefined
a = 1;
console.log(a); //

如上述範例中 真正在 Javascript 執行的結果應該是

var a, f ;

f = function(a){
console.log(a);
}
console.log(f); // functioin  f

console.log(a); // undefined
a = 1;
console.log(a); //

所以當我們在宣告 f 之前執行的話就會得到一個錯誤訊息

f is undefined not a funciton

為什麼不要使用 Var

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

 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 變數在雙迴圈中會被互相覆蓋造成程式執行上的錯誤,不會依據我們預想的去執行

奇怪的問題

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

但是結過卻是

10
10
10
10
10
10
10
10
10
10

但是我們希望的是

0
1
2
3
4
5
6
7
8
9

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

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

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

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

Const 和 Let

let

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

let hello = 'hello';
Block-scoping

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

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 宣告的範例如下

try{
  throw 'oh no!';
}catch(e){
  console.log('oh well');
}

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

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

a++;
let a;

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

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

function foo(){
  return a;
}

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

 let a;
重複宣告和 shadowing

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

function f(x){
  var x;
  var x;
  if(true){
    var x;
  }
}

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

let x = 10;
let x = 20; // Error
function f(x){
  let x = 100;// Error
}

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

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

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

例如:

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 使用已宣告的變數的時候,即使脫離了那個作用域,也是依舊可以使用該變數

function theCityThatAlwaysSleeps(){
  let getCity;
  if(true){
    let city = 'Seattle';
    getCity = function (){
      return city;
    }
  }

  return getCity(); // Seattle
}

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

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

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

結果為

0
1
2
3
4
5
6
7
8
9

const

const 是另外一種不同的宣告

const numLivesForCat = 9;

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

const 宣告的變數是 immutable

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 來宣告


上一篇
Day01 - Install TypeScript
下一篇
Day 03 - Function
系列文
TypeScript - 初學之路8

1 則留言

0
javascript
iT邦新手 2 級 ‧ 2017-12-11 08:59:25

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

部 → 不

for(var i =0;i < currentRow.length; i++>){
sum += currentRow[i];
}

多了 >

使用 let 宣告的時候,它的作用域市 blocking-scope。

市 → 是

我要留言

立即登入留言