鍍金池/ 教程/ HTML/ 變量聲明
初始化項目結(jié)構(gòu)
聯(lián)合類型
介紹
介紹
介紹
編譯選項
TypeScript 1.6
介紹
介紹
發(fā)展路線圖
介紹
在MSBuild里使用編譯選項
可迭代性
TypeScript 1.3
介紹
介紹
TypeScript 1.1
變量聲明
即將到來的Angular 2框架是使用TypeScript開發(fā)的。 因此Angular和TypeScript一起使用非常簡單方便
tsconfig.json
介紹
介紹
介紹
在MSBuild里使用編譯選項
使用TypeScript的每日構(gòu)建版本
新建工程
枚舉
三斜線指令
結(jié)合ASP.NET v5使用TypeScript
TypeScript里的this
介紹
TypeScript 1.4
編碼規(guī)范
介紹
模塊解析
ASP.NET 4
架構(gòu)概述
介紹
介紹
ASP.NET Core
TypeScript 1.8
介紹
介紹
創(chuàng)建簡單工程
TypeScript 1.7
TypeScript 1.5
NPM包的類型
支持TypeScript的編輯器

變量聲明

letconst是JavaScript里相對較新的變量聲明方式。 像我們之前提到過的,let在很多方面與var是相似的,但是可以幫助大家避免在JavaScript里常見一些問題。 const是對let的一個增強,它能阻止對一個變量再次賦值。

因為TypeScript是JavaScript的超集,所以它本身就支持letconst。 下面我們會詳細說明這些新的聲明方式以及為什么推薦使用它們來代替var。

如果你之前使用JavaScript時沒有特別在意,那么這節(jié)內(nèi)容會喚起你的回憶。 如果你已經(jīng)對var聲明的怪異之處了如指掌,那么你可以輕松地略過這節(jié)。

var 聲明

一直以來我們都是通過var關(guān)鍵字定義JavaScript變量。

var a = 10;

大家都能理解,這里定義了一個名為a值為10的變量。

我們也可以在函數(shù)內(nèi)部定義變量:

function f() {
    var message = "Hello, world!";

    return message;
}

并且我們也可以在其它函數(shù)內(nèi)部訪問相同的變量。

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

var g = f();
g(); // returns 11;

上面的例子里,g可以獲取到f函數(shù)里定義的a變量。 每當(dāng)g被調(diào)用時,它都可以訪問到f里的a變量。 即使當(dāng)gf已經(jīng)執(zhí)行完后才被調(diào)用,它仍然可以訪問及修改a。

function f() {
    var a = 1;

    a = 2;
    var b = g();
    a = 3;

    return b;

    function g() {
        return a;
    }
}

f(); // returns 2

作用域規(guī)則

對于熟悉其它語言的人來說,var聲明有些奇怪的作用域規(guī)則。 看下面的例子:

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

    return x;
}

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

有些讀者可能要多看幾遍這個例子。 變量x是定義在if語句里面,但是我們卻可以在語句的外面訪問它。 這是因為var聲明可以在包含它的函數(shù),模塊,命名空間或全局作用域內(nèi)部任何位置被訪問(我們后面會詳細介紹),包含它的代碼塊對此沒有什么影響。 有些人稱此為var作用域函數(shù)作用域。 函數(shù)參數(shù)也使用函數(shù)作用域。

這些作用域規(guī)則可能會引發(fā)一些錯誤。 其中之一就是,多次聲明同一個變量并不會報錯:

function sumMatrix(matrix: number[][]) {
    var sum = 0;
    for (var i = 0; i < matrix.length; i++) {
        var currentRow = matrix[i];
        for (var i = 0; i < currentRow.length; i++) {
            sum += currentRow[i];
        }
    }

    return sum;
}

這里很容易看出一些問題,里層的for循環(huán)會覆蓋變量i,因為所有i都引用相同的函數(shù)作用域內(nèi)的變量。 有經(jīng)驗的開發(fā)者們很清楚,這些問題可能在代碼審查時漏掉,引發(fā)無窮的麻煩。

變量獲取怪異之處

快速的猜一下下面的代碼會返回什么:

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

介紹一下,setTimeout會在若干毫秒的延時后執(zhí)行一個函數(shù)(等待其它代碼執(zhí)行完畢)。

好吧,看一下結(jié)果:

10
10
10
10
10
10
10
10
10
10

很多JavaScript程序員對這種行為已經(jīng)很熟悉了,但如果你很不解,你并不是一個人。 大多數(shù)人期望輸出結(jié)果是這樣:

0
1
2
3
4
5
6
7
8
9

還記得我們上面講的變量獲取嗎?

每當(dāng)g被調(diào)用時,它都可以訪問到f里的a變量。

讓我們花點時間考慮在這個上下文里的情況。 setTimeout在若干毫秒后執(zhí)行一個函數(shù),并且是在for循環(huán)結(jié)束后。 for循環(huán)結(jié)束后,i的值為10。 所以當(dāng)函數(shù)被調(diào)用的時候,它會打印出10

一個通常的解決方法是使用立即執(zhí)行的函數(shù)表達式(IIFE)來捕獲每次迭代時i的值:

for (var i = 0; i < 10; i++) {
    // capture the current state of 'i'
    // by invoking a function with its current value
    (function(i) {
        setTimeout(function() { console.log(i); }, 100 * i);
    })(i);
}

這種奇怪的形式我們已經(jīng)司空見慣了。 參數(shù)i會覆蓋for循環(huán)里的i,但是因為我們起了同樣的名字,所以我們不用怎么改for循環(huán)體里的代碼。

let 聲明

現(xiàn)在你已經(jīng)知道了var存在一些問題,這恰好說明了為什么用let語句來聲明變量。 除了名字不同外,letvar的寫法一致。

let hello = "Hello!";

主要的區(qū)別不在語法上,而是語義,我們接下來會深入研究。

塊作用域

當(dāng)用let聲明一個變量,它使用的是詞法作用域塊作用域。 不同于使用var聲明的變量那樣可以在包含它們的函數(shù)外訪問,塊作用域變量在包含它們的塊或for循環(huán)之外是不能訪問的。

function f(input: boolean) {
    let a = 100;

    if (input) {
        // Still okay to reference 'a'
        let b = a + 1;
        return b;
    }

    // Error: 'b' doesn't exist here
    return b;
}

這里我們定義了2個變量ab。 a的作用域是f函數(shù)體內(nèi),而b的作用域是if語句塊里。

catch語句里聲明的變量也具有同樣的作用域規(guī)則。

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

// Error: 'e' doesn't exist here
console.log(e);

擁有塊級作用域的變量的另一個特點是,它們不能在被聲明之前讀或?qū)憽?雖然這些變量始終“存在”于它們的作用域里,但在直到聲明它的代碼之前的區(qū)域都屬于時間死區(qū)。 它只是用來說明我們不能在let語句之前訪問它們,幸運的是TypeScript可以告訴我們這些信息。

a++; // illegal to use 'a' before it's declared;
let a;

注意一點,我們?nèi)匀豢梢栽谝粋€擁有塊作用域變量被聲明前獲取它。 只是我們不能在變量聲明前去調(diào)用那個函數(shù)。 如果生成代碼目標(biāo)為ES2015,現(xiàn)代的運行時會拋出一個錯誤;然而,現(xiàn)今TypeScript是不會報錯的。

function foo() {
    // okay to capture 'a'
    return a;
}

// 不能在'a'被聲明前調(diào)用'foo'
// 運行時應(yīng)該拋出錯誤
foo();

let a;

關(guān)于時間死區(qū)的更多信息,查看這里Mozilla Developer Network.

重定義及屏蔽

我們提過使用var聲明時,它不在乎你聲明多少次;你只會得到1個。

function f(x) {
    var x;
    var x;

    if (true) {
        var x;
    }
}

在上面的例子里,所有x的聲明實際上都引用一個相同x,并且這是完全有效的代碼。 這經(jīng)常會成為bug的來源。 好的是,let聲明就不會這么寬松了。

let x = 10;
let x = 20; // 錯誤,不能在1個作用域里多次聲明`x`

并不是要求兩個均是塊級作用域的聲明TypeScript才會給出一個錯誤的警告。

function f(x) {
    let x = 100; // error: interferes with parameter declaration
}

function g() {
    let x = 100;
    var x = 100; // error: can't have both declarations of 'x'
}

并不是說塊級作用域變量不能在函數(shù)作用域內(nèi)聲明。 而是塊級作用域變量需要在不用的塊里聲明。

function f(condition, x) {
    if (condition) {
        let x = 100;
        return x;
    }

    return x;
}

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

在一個嵌套作用域里引入一個新名字的行為稱做屏蔽。 它是一把雙刃劍,它可能會不小心地引入新問題,同時也可能會解決一些錯誤。 例如,假設(shè)我們現(xiàn)在用let重寫之前的sumMatrix函數(shù)。

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;
}

這個版本的循環(huán)能得到正確的結(jié)果,因為內(nèi)層循環(huán)的i可以屏蔽掉外層循環(huán)的i。

通常來講應(yīng)該避免使用屏蔽,因為我們需要寫出清晰的代碼。 同時也有些場景適合利用它,你需要好好打算一下。

塊級作用域變量的獲取

在我們最初談及獲取用var聲明的變量時,我們簡略地探究了一下在獲取到了變量之后它的行為是怎樣的。 直觀地講,每次進入一個作用域時,它創(chuàng)建了一個變量的環(huán)境。 就算作用域內(nèi)代碼已經(jīng)執(zhí)行完畢,這個環(huán)境與其捕獲的變量依然存在。

function theCityThatAlwaysSleeps() {
    let getCity;

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

    return getCity();
}

因為我們已經(jīng)在city的環(huán)境里獲取到了city,所以就算if語句執(zhí)行結(jié)束后我們?nèi)匀豢梢栽L問它。

回想一下前面setTimeout的例子,我們最后需要使用立即執(zhí)行的函數(shù)表達式來獲取每次for循環(huán)迭代里的狀態(tài)。 實際上,我們做的是為獲取到的變量創(chuàng)建了一個新的變量環(huán)境。 這樣做挺痛苦的,但是幸運的是,你不必在TypeScript里這樣做了。

當(dāng)let聲明出現(xiàn)在循環(huán)體里時擁有完全不同的行為。 不僅是在循環(huán)里引入了一個新的變量環(huán)境,而是針對每次迭代都會創(chuàng)建這樣一個新作用域。 這就是我們在使用立即執(zhí)行的函數(shù)表達式時做的事,所以在setTimeout例子里我們僅使用let聲明就可以了。

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

會輸出與預(yù)料一致的結(jié)果:

0
1
2
3
4
5
6
7
8
9

const 聲明

const 聲明是聲明變量的另一種方式。

const numLivesForCat = 9;

它們與let聲明相似,但是就像它的名字所表達的,它們被賦值后不能再改變。 換句話說,它們擁有與let相同的作用域規(guī)則,但是不能對它們重新賦值。

這很好理解,它們引用的值是不可變的。

const numLivesForCat = 9;
const kitty = {
    name: "Aurora",
    numLives: numLivesForCat,
}

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

// all "okay"
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;

除非你使用特殊的方法去避免,實際上const變量的內(nèi)部狀態(tài)是可修改的。

let vs. const

現(xiàn)在我們有兩種作用域相似的聲明方式,我們自然會問到底應(yīng)該使用哪個。 與大多數(shù)泛泛的問題一樣,答案是:依情況而定。

使用最小特權(quán)原則,所有變量除了你計劃去修改的都應(yīng)該使用const。 基本原則就是如果一個變量不需要對它寫入,那么其它使用這些代碼的人也不能夠?qū)懭胨鼈儯⑶乙伎紴槭裁磿枰獙@些變量重新賦值。 使用const也可以讓我們更容易的推測數(shù)據(jù)的流動。

另一方面,用戶很喜歡let的簡潔性。 這個手冊大部分地方都使用了let。

跟據(jù)你的自己判斷,如果合適的話,與團隊成員商議一下。

解構(gòu)

Another TypeScript已經(jīng)可以解析其它 ECMAScript 2015 特性了。 完整列表請參見 the article on the Mozilla Developer Network。 本章,我們將給出一個簡短的概述。

解構(gòu)數(shù)組

最簡單的解構(gòu)莫過于數(shù)組的解構(gòu)賦值了:

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

這創(chuàng)建了2個命名變量 firstsecond。 相當(dāng)于使用了索引,但更為方便:

first = input[0];
second = input[1];

解構(gòu)作用于已聲明的變量會更好:

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

作用于函數(shù)參數(shù):

function f([first, second]: [number, number]) {
    console.log(first);
    console.log(second);
}
f(input);

你可以使用...name語法創(chuàng)建一個剩余變量列表:

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

當(dāng)然,由于是JavaScript, 你可以忽略你不關(guān)心的尾隨元素:

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

或其它元素:

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

對象解構(gòu)

你也可以解構(gòu)對象:

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

這通過 o.a and o.b 創(chuàng)建了 ab 。 注意,如果你不需要 c 你可以忽略它。

就像數(shù)組解構(gòu),你可以用沒有聲明的賦值:

({a, b} = {a: "baz", b: 101});

注意,我們需要用括號將它括起來,因為Javascript通常會將以 { 起始的語句解析為一個塊。

屬性重命名

你也可以給屬性以不同的名字:

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

這里的語法開始變得混亂。 你可以將 a: newName1 讀做 "a 作為 newName1"。 方向是從左到右,好像你寫成了以下樣子:

let newName1 = o.a;
let newName2 = o.b;

令人困惑的是,這里的冒號不是指示類型的。 如果你想指定它的類型, 仍然需要在其后寫上完整的模式。

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

默認值

默認值可以讓你在屬性為 undefined 時使用缺省值:

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

現(xiàn)在,即使 b 為 undefined , keepWholeObject 函數(shù)的變量 wholeObject 的屬性 ab 都會有值。

函數(shù)聲明

解構(gòu)也能用于函數(shù)聲明。 看以下簡單的情況:

type C = {a: string, b?: number}
function f({a, b}: C): void {
    // ...
}

但是,通常情況下更多的是指定默認值,解構(gòu)默認值有些棘手。 首先,你需要知道在設(shè)置默認值之前設(shè)置其類型。

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

其次,你需要知道在解構(gòu)屬性上給予一個默認或可選的屬性用來替換主初始化列表。 要知道 C 的定義有一個 b 可選屬性:

function f({a, b = 0} = {a: ""}): void {
    // ...
}
f({a: "yes"}) // ok, default b = 0
f() // ok, default to {a: ""}, which then defaults b = 0
f({}) // error, 'a' is required if you supply an argument

要小心使用解構(gòu)。 從前面的例子可以看出,就算是最簡單的解構(gòu)也會有很多問題。 尤其當(dāng)存在深層嵌套解構(gòu)的時候,就算這時沒有堆疊在一起的重命名,默認值和類型注解,也是令人難以理解的。 解構(gòu)表達式要盡量保持小而簡單。 你自己也可以直接使用解構(gòu)將會生成的賦值表達式。

上一篇:創(chuàng)建簡單工程下一篇:介紹