TypeScript基礎(chǔ)(二) 變量說明

這一篇文章主要是來探討,為什么要用const和let替代var娄猫,還有ES6新特性:解構(gòu)

js里面很多時候媳溺,var關(guān)鍵字寫與不寫對運行結(jié)果沒有多大關(guān)系悬蔽,可是如果實在嚴(yán)格模式下就不能運行捉兴,所以ts要規(guī)范js的編碼風(fēng)格,使之要像面向?qū)ο笳Z言一樣嚴(yán)謹(jǐn)禾乘,塊級區(qū)域概念深入骨髓始藕,接下來我們就來看看伍派,為什么要替換掉var

var 應(yīng)用場景

一般場景

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

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

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

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

作用域規(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ù)(function),模塊(閉包纲堵,module),命名空間(namespace)或全局內(nèi)部任何位置(window)被訪問铐望,而這里的if只是一個小小的代碼塊正蛙,對此沒有影響,有些人稱此為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);
}

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

10
10
10
10
10
10
10
10
10
10

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

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

setTimeOut是一個異步的方法,所以for代碼塊很快執(zhí)行完闲礼,里面的方法就需要等待之后才會執(zhí)行牍汹,而且每次執(zhí)行都是訪問同一個內(nèi)存地址(i的地址),所以最終的結(jié)果就都是10

不過在js里面對于這種情況為我們還是有對應(yīng)的解決方案的柬泽,那就是使用閉包來解決慎菲,動態(tài)為每個setTimeOut分配一個內(nèi)存塊存儲相關(guān)的臨時變量,這樣就可以解決上面借結(jié)果都是10的問題了锨并。

for (var i = 0; i < 10; i++) {
    (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語句來聲明變量。 除了名字不同外雕凹, let與var的寫法一致赠幕。

let hello = "Hello!";

記住一個概念:你要用規(guī)范的風(fēng)格來編寫TypeScript

塊作用域

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

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

    if (input) {
        // 這里是可以訪問到a的
        let b = a + 1;
        return b;
    }

    // 報錯:變量b沒有定義
    return b;
}

重定義及屏蔽

我們提過使用var聲明時搁进,它不在乎你聲明多少次匆瓜;你只會得到最新聲明的那一個。

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; // 錯誤: 變量x已經(jīng)定義
}

function g() {
    let x = 100;
    var x = 100; // 錯誤: 不能重復(fù)定義變量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

塊級作用域變量的獲取

在我們最初談及獲取用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);
}

其結(jié)果與我們預(yù)想的一模一樣

0
1
2
3
4
5
6
7
8
9

const 聲明

const 聲明是聲明變量的另一種方式,它們擁有與 let相同的作用域規(guī)則必峰,但是不能對它們重新賦值粒蜈。

它們引用的值是不可變的

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

// 錯誤顺献,因為你修改了內(nèi)存塊地址,就修改了引用的值
kitty = {
    name: "Danielle",
    numLives: numLivesForCat
};

// 以下結(jié)果都正確枯怖,因為只會修改內(nèi)存塊的值注整,沒有修改引用的值(內(nèi)存塊地址)
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)

如果是第一看解構(gòu)的代碼审孽,可能會有點懵逼,到底是什么鬼啊浑娜,因為這個是ES6的新語法佑力。

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

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

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

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

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

或其它元素:

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

對象解構(gòu)

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

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

屬性重命名

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

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;

默認(rèn)值

這是ES6里面的新特性响驴,如果編譯器沒有環(huán)境透且,或瀏覽器不支持,建議使用最新版Chrome瀏覽器調(diào)試

默認(rèn)值可以讓你在屬性為 undefined 時使用缺省值:

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

函數(shù)聲明

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

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

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

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

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

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

function f({a, b = 0} = {a: ""}): void {
    // ...
}
f({a: "yes"}) // 正確: a默認(rèn)值為"yes"束析,b默認(rèn)值為0
f() // 正確:先設(shè)置默認(rèn)值a為"",然后設(shè)置默認(rèn)值b為0
f({}) // 錯誤:a參數(shù)缺少

我想有很多小伙伴會問,最后f({})怎么會出錯呢憎亚?我不是設(shè)置了默認(rèn)值的嗎员寇?

首先我們來看一看,里面默認(rèn)值的設(shè)置有哪幾步第美?
首先:{a, b = 0},這是默認(rèn)值設(shè)置第一步蝶锋,先在符號表里面設(shè)置a或b的默認(rèn)值,

接下來:{a: ""}什往,這是默認(rèn)值設(shè)置第二部扳缕,緊接著更新符號表的默認(rèn)值

我們可以看一看下面的小程序:

function f({a=3,b="bbbb"}={b:"sdfsdfs"}){
    console.log(a+"---------"+b)
}
//輸出為 3---------sdfsdfs

是不是感覺解構(gòu)很強大呢?哈哈,不過要小心使用解構(gòu)躯舔。 從前面的例子可以看出驴剔,就算是最簡單的解構(gòu)也會有很多問題。 尤其當(dāng)存在深層嵌套解構(gòu)的時候粥庄,就算這時沒有堆疊在一起的重命名丧失,默認(rèn)值和類型注解,也是令人難以理解的惜互。 解構(gòu)表達式要盡量保持小而簡單布讹。 你自己也可以直接使用解構(gòu)將會生成的賦值表達式。

【翻譯:原文地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末训堆,一起剝皮案震驚了整個濱河市描验,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坑鱼,老刑警劉巖膘流,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鲁沥,居然都是意外死亡睡扬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門黍析,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人屎开,你說我怎么就攤上這事阐枣。” “怎么了奄抽?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵蔼两,是天一觀的道長。 經(jīng)常有香客問我逞度,道長额划,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任档泽,我火速辦了婚禮俊戳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘馆匿。我一直安慰自己抑胎,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布渐北。 她就那樣靜靜地躺著阿逃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恃锉,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天搀菩,我揣著相機與錄音,去河邊找鬼破托。 笑死肪跋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的炼团。 我是一名探鬼主播澎嚣,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瘟芝!你這毒婦竟也來了易桃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤锌俱,失蹤者是張志新(化名)和其女友劉穎晤郑,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贸宏,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡造寝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吭练。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诫龙。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鲫咽,靈堂內(nèi)的尸體忽然破棺而出签赃,到底是詐尸還是另有隱情,我是刑警寧澤分尸,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布锦聊,位于F島的核電站,受9級特大地震影響箩绍,放射性物質(zhì)發(fā)生泄漏孔庭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一材蛛、第九天 我趴在偏房一處隱蔽的房頂上張望圆到。 院中可真熱鬧,春花似錦卑吭、人聲如沸构资。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吐绵。三九已至迹淌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間己单,已是汗流浹背唉窃。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纹笼,地道東北人纹份。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像廷痘,于是被迫代替她去往敵國和親蔓涧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 特別說明笋额,為便于查閱元暴,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 562評論 0 0
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點點福利:阿里云產(chǎn)品券兄猩,享受所有官網(wǎng)優(yōu)惠茉盏,并抽取幸運大...
    HetfieldJoe閱讀 3,025評論 3 37
  • 本文屬個人筆記淹真,不做詳解讶迁,僅供參考! let命令 基本用法 ES6 新增了let命令核蘸,用來聲明變量巍糯。它的用法類似于...
    R_yan閱讀 28,993評論 6 18
  • 碧水青山綠羅裙,(目測) 朱顏紅妝點絳唇值纱;(腦補) 門里秋千門前海,(假裝) 一俯一仰一乾坤坯汤。(狂想) 注:安師姐...
    林宏海閱讀 173評論 0 0
  • 寫于2015年10月 前幾天回家用手機拍下了這張家鄉(xiāng)的照片虐唠。 我的家鄉(xiāng)是一個坐落在山前沖積臺地上的小山村,人口稀疏...
    敏敏的日記閱讀 781評論 1 2