TypeScript 變量聲明
本節(jié)介紹 var
let
const
這三種變量的聲明方式,重點(diǎn)討論作用域與變量提升的相關(guān)知識(shí)點(diǎn),這部分往往也是面試??疾糠郑枰嗉幼⒁?。
說明: 在使用 TypeScript 進(jìn)行變量聲明時(shí),一般建議添加對(duì)應(yīng)的變量類型,由于本節(jié)是整個(gè)系列教程的第一節(jié),還未介紹 TypeScript 的類型系統(tǒng),所以本節(jié)的例子仍采用 JavaScript 的變量聲明方式。雖然在變量聲明時(shí)沒有添加變量類型,但是 TypeScript 會(huì)自動(dòng)進(jìn)行類型推導(dǎo),得到正確的變量類型,這個(gè)在后續(xù)章節(jié)也會(huì)專門介紹。
1. 慕課解釋
TypeScript 是 JavaScript 的超集,同 JavaScript 一樣,聲明變量可以采用下面三個(gè)關(guān)鍵字:
- var
- let
- const
2. var 聲明
通過 var
關(guān)鍵字來定義 JavaScript 變量,這個(gè)大家都能理解:
var num = 10
2.1 作用域
下面我們來討論一個(gè)為什么盡量避免使用 var
。
快速的猜一下下面的代碼會(huì)返回什么:
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i)
}, 100 * i)
}
相信很多人都踩過這個(gè)坑,這幾乎是作用域的必考題目,但如果你不了解,不要擔(dān)心,你并不是一個(gè)人。大多數(shù)人期望輸出的結(jié)果是這樣的:
0
1
2
3
4
5
6
7
8
9
然而,實(shí)際結(jié)果是這樣的:
10
10
10
10
10
10
10
10
10
10
解釋:這里的 i
是在全局作用域中的,只存在一個(gè)值。 setTimeout
這個(gè)異步函數(shù)在若干毫秒后執(zhí)行,并且它是在 for
循環(huán)結(jié)束后的。在 for
循環(huán)結(jié)束后, i
的值為 10
,所以當(dāng)函數(shù)被調(diào)用的時(shí)候,它會(huì)打印出 10
。
下面介紹幾種可以實(shí)現(xiàn)我們預(yù)期輸出結(jié)果的方法:
- 通過調(diào)用函數(shù),創(chuàng)建函數(shù)作用域:
for (var i = 0; i < 10; i++) {
f(i)
}
function f (i) {
setTimeout(function () {
console.log(i)
}, 100)
}
- 立即執(zhí)行函數(shù),創(chuàng)建函數(shù)作用域:
for (var i = 0; i < 10; i++) {
(function (i) {
setTimeout(function () {
console.log(i)
}, 100 * i)
})(i)
}
- 通過
let
關(guān)鍵字創(chuàng)建塊級(jí)作用域:
for (let i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i)
}, 100 * i)
}
2.2 變量提升
看下面的例子:
console.log(a) // undefined
var a = 1
它等價(jià)于:
var a
console.log(a) // undefined
a = 1
從上面的例子中可以看出,關(guān)鍵詞 var
會(huì)進(jìn)行變量提升。
如果把上面的 var
換成 let
呢?
console.log(a) // ReferenceError: a is not defined
let a = 1
會(huì)報(bào)錯(cuò) a is not defined
未定義,這里也可以看出 var
與 let
在變量提升的不同:
- var 會(huì)將變量的創(chuàng)建和初始化都進(jìn)行提升
- let 只會(huì)將創(chuàng)建提升,而初始化未被提升,稱之為暫時(shí)性死區(qū)
3. let 聲明
現(xiàn)在你已經(jīng)知道了 var
存在全局作用域和變量提升的問題,這恰好說明了為什么用 let
語句來聲明變量。
let num = 10
下面介紹 let
的一些特點(diǎn):
3.1 塊作用域
在 ES6 之前,ECMAScript 的作用域只有兩種:
- 全局作用域
- 函數(shù)作用域
現(xiàn)在通過命令 let 和 const 新增了“塊級(jí)作用域”,定義在代碼塊中的變量在代碼塊被執(zhí)行結(jié)束后會(huì)被釋放掉:
function block() {
if (true) {
let a = 10
console.log(a) // 10
}
console.log(a) // Cannot find name 'a'.ts(2304)
}
代碼解釋:
第 3 - 4 行,a 變量在 if{} 代碼塊中是有效的,正常輸出 10。
第7行,在 if{} 代碼塊外就被釋放掉了,所以會(huì)報(bào)錯(cuò)誤。
包括在一對(duì)花括號(hào)中的一組語句稱之為”代碼塊“,它可以替換掉上面介紹 var 時(shí)使用的立即執(zhí)行函數(shù):
// 立即執(zhí)行函數(shù)
(function(){
var a = 10
}())
// 代碼塊
{
let a = 10
}
3.2 重定義
在使用 var
聲明時(shí),不論聲明幾次,最終只會(huì)得到一個(gè):
var x
var x
var x
這是一個(gè)完全有效的代碼,所有 x
的聲明其實(shí)都指向了同一個(gè)引用,但這也是很多 bug 產(chǎn)生的原因。 let
的聲明就嚴(yán)格了許多:
let x
let x // Cannot redeclare block-scoped variable 'x'
代碼解釋: 第 2 行,重新聲明了塊作用域的變量 x 。
4. const 聲明
關(guān)鍵字 const
聲明變量,它被賦值后不能再改變。 換句話說,它擁有與 let
相同的作用域規(guī)則,但是不能重新賦值。
用 const 聲明變量,并不是變量的值不得改動(dòng),而是變量指向的那個(gè)內(nèi)存地址不得改動(dòng)。用 const 聲明初始數(shù)據(jù)類型如布爾值、數(shù)字、字符串,可以理解為聲明常量,因?yàn)檫@些初始類型的值就保存在變量所指向的那個(gè)內(nèi)存地址。
const num = 10
const brand = 'imooc'
const registered = true
num = 20 // Cannot assign to 'num' because it is a constant.ts(2588)
代碼解釋: 第 5 行,在給 num 第二次賦值時(shí)會(huì)報(bào)錯(cuò)。
對(duì)于復(fù)合類型的數(shù)據(jù)來說,變量所指向的內(nèi)存地址保存的只是一個(gè)指針,const 能夠保證其指針不變,但屬性值是可變的:
const person = {
name: 'Tom',
address: 'Baker Street 221b'
}
// error
person = {
name: 'Sherlock',
address: 'Baker Street 221b'
}
// ok
person.name = 'Sherlock'
代碼解釋: 第 7 行,對(duì)已經(jīng)使用 const 聲明的變量重新賦值報(bào)錯(cuò),但是在第 13 行,只是對(duì)這個(gè)對(duì)象的屬性賦值是可以的。
5. 小結(jié)
閱讀本小節(jié)中三種不同的變量聲明,我們知道了:
let
和count
實(shí)現(xiàn)了塊級(jí)作用域。- 所有變量除了你計(jì)劃去修改的都應(yīng)該使用
const
。 - 盡量使用
let
和const
來聲明變量,減少var
的使用。