這一篇文章主要是來探討,為什么要用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)將會生成的賦值表達式。
【翻譯:原文地址】