每一種程式語言的第一步都是從宣告變數開始
但是 TypeScript
其實最後編譯結果還是 Javascript
我們還是以 Javascript
的宣告變數基礎來談談
而類型控制之後再說吧
let
和 const
是兩種 Javascript 新的宣告方式, let
和 var
比較類似
const
則是定義之後禁止之後修改(常數)
TypeScript
也有提供 let
和 const
的宣告方式,下個部分將會解釋為什麼會推薦使用 let
和 const
在 Javascript 中宣告一個變數常用的方式是
var a = 10;
在上面的例子之中,你宣告了一個變數 a
為 10
你也可以再 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
而結果是 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
這個規則可能會造成壹些不同種類型的錯誤,其中一種就是當你重複宣告同樣名稱的變數的時候彼此會互相覆蓋
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 中卻是常見解決這個問題的方式
現在你已經知道 var
會有一些問題,所以會有一些問題,所以為什麼需要介紹 let
.let
和 var
的使用方式依樣
let hello = 'hello';
使用 let
宣告的時候,它的作用域市 blocking-scope
。和 var
宣告的作用域不一樣,他是用大括號來做區隔
function f(input: boolean){
let a = 100;
if(input){
let b = a + 1;
return b;
}
return b; // b 並不存在
}
上述範例中有 a
和 b
兩個變數, 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;
若是使用 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
在攥寫程式碼的時候應該要避免的狀況之一
當我們在一個作用域中宣告一個變數與 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 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--;
除非你要整個複寫整個物件,否則還是可以修改參數值得,
也就是此物件性質為 唯讀
的,詳情參閱
為什麼需要兩個不同的語意卻擁有相同的 block-scoping
的宣告方式呢?
基於 最小權限原則
若之後變數都不需要修改或是物件僅僅提供修改參數的權限時,則使用 const
,換句話說若是變數之後有可能會被覆寫則使用 let
來宣告