let 和const是es6新增的命令亡驰,用于聲明變量
var和let/const的區(qū)別
1.塊級(jí)作用域
2.不存在變量提升
3.暫時(shí)性死區(qū)
4.不可重復(fù)聲明
5.let const聲明的全局變量不會(huì)掛在頂層對(duì)象下面
const 需注意倆點(diǎn):
- const 聲明之后必須馬上賦值赡艰,否則會(huì)報(bào)錯(cuò)
2.const簡(jiǎn)單類型一旦聲明就不能再更改宠叼,復(fù)雜類型(數(shù)組舍沙,對(duì)象等)指針指向的地址不能更改宗兼,但是內(nèi)部數(shù)據(jù)可以更改咆耿。
塊級(jí)作用域的出現(xiàn)
通過(guò)var聲明的變量存在變量提升的特性:
if (condition) {
var value = 1;
}
console.log(value);
如果condition為false杆煞,結(jié)果并沒(méi)有報(bào)錯(cuò)娩嚼,而是輸出undefined, 因?yàn)樽兞刻嵘脑颍?代碼相當(dāng)于:
var value;
if (condition) {
value = 1;
}
console.log(value);
除此之外蘑险,在for循環(huán)中:
for (var i = 0; i < 10; i++) {
...
}
console.log(i); // 10
即便循環(huán)已經(jīng)結(jié)束了,我們依然可以訪問(wèn)i的值岳悟。
為了加強(qiáng)對(duì)變量生命周期的控制佃迄, ES6引入了塊級(jí)作用域。
塊級(jí)作用域存在于:
- 函數(shù)內(nèi)部
- 塊中()字符和{}之間的區(qū)域
let 和 const
塊級(jí)聲明用于聲明在指定塊的作用域之外無(wú)法訪問(wèn)的變量贵少。
let 和 const 都是塊級(jí)聲明的一種呵俏。
let 和 const 的特點(diǎn):
1. 不會(huì)被提升
if (false) {
let value = 1;
}
console.log(value); // Uncaught ReferenceError: value is not defined
2. 重復(fù)聲明報(bào)錯(cuò)
var value = 1;
let value = 2; // Uncaught SyntaxError: Identifier 'value' has already been declared
3. 不綁定全局作用域
當(dāng)在全局作用域中使用var聲明的時(shí)候,會(huì)創(chuàng)建一個(gè)新的全局變量作為全局對(duì)象的屬性
var value = 1;
console.log(window.value); // 1
然而let和const不會(huì)
let value = 1;
console.log(window.value); // undefined
再來(lái)說(shuō)下let和const的區(qū)別
const用于聲明常量滔灶,其值一旦被設(shè)定不能再被修改普碎,否則會(huì)報(bào)錯(cuò)。
但是const聲明復(fù)雜類型時(shí)录平,const不允許修改綁定麻车,但允許修改值缀皱, 比如當(dāng)用const聲明對(duì)象時(shí):
const data = {
value: 1
}
// 沒(méi)有問(wèn)題
data.value = 2;
data.num = 3;
// 報(bào)錯(cuò)
data = {}; // Uncaught TypeError: Assignment to constant variable.
臨時(shí)死區(qū)
let 和 const聲明的變量不會(huì)被提升到作用域頂部,如果在聲明之前訪問(wèn)這些變量动猬,會(huì)導(dǎo)致報(bào)錯(cuò):
console.log(typeof value); // Uncaught ReferenceError: value is not defined
let value = 1;
這是因?yàn)镴avaScript引擎在掃描代碼發(fā)現(xiàn)變量聲明時(shí)啤斗,要么將它們提升到作用域頂部(遇到var聲明),要么將聲明放在TDZ中(臨時(shí)死區(qū)中)(遇到let和const聲明)赁咙。訪問(wèn)TDZ中的變量會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤钮莲。只有執(zhí)行過(guò)變量聲明語(yǔ)句后,變量才會(huì)從TDZ中移除彼水,然后方可訪問(wèn)
var value = "global";
// 例子1
(function() {
console.log(value);
let value = 'local';
}());
// 例子2
{
console.log(value);
const value = 'local';
};
兩個(gè)例子中臂痕,結(jié)果都報(bào)錯(cuò):Uncaught ReferenceError: value is not defined, 就是因?yàn)門DZ緣故
總之猿涨,在代碼塊內(nèi)握童,使用let命令聲明變量之前股淡,該變量都是不可用的痢畜。 這在語(yǔ)法上稱為“暫時(shí)性死區(qū)”
循環(huán)中的塊級(jí)作用域
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // 3
解決方案:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(i){
return function() {
console.log(i);
}
}(i))
}
funcs[0](); // 0
ES6的let為這個(gè)問(wèn)題提供了新的解決方案:
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // 0
問(wèn)題在于,上面講了let不提升滑蚯,不能重復(fù)聲明俺附,不能綁定全局作用域等特性肥卡,可是為什么
在這里能打印出i值呢?
如果是不重復(fù)聲明事镣,在循環(huán)第二次的時(shí)候步鉴, 又用let聲明了i, 應(yīng)該報(bào)錯(cuò)呀氛琢, 就算因?yàn)槟撤N原因阳似,重復(fù)聲明不報(bào)錯(cuò),一遍一遍迭代, i 的值最終還是應(yīng)該是3呀畜吊,因?yàn)閒or循環(huán)設(shè)置循環(huán)變量那部分是一個(gè)單獨(dú)的作用域,就比如:
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
如果我們把let 改成 var 呢
for (var i = 0; i < 3; i++) {
var i = 'abc';
console.log(i);
}
// abc
// 這里輸出一次一個(gè)abc, 是因?yàn)閒or()里面使用var時(shí)沒(méi)有在()內(nèi)形成封閉的作用域,i 被循環(huán)體賦值成‘a(chǎn)bc’, !!('abc' < 3) === false 循環(huán)停止了恋腕。
究其原因获高,是因?yàn)閘et聲明在循環(huán)內(nèi)部的行為是標(biāo)準(zhǔn)中專門定義的,不一定就與let的不提升特性有關(guān)币狠。其實(shí)肛炮,在早期的let實(shí)現(xiàn)中就不包含這一行為碍扔。
在for循環(huán)中使用let和var悲幅,底層會(huì)使用不同的處理方式。
那么當(dāng)使用let的時(shí)候底層到底發(fā)生了什么
簡(jiǎn)單的來(lái)說(shuō), 就是在 for(let i=0; i<3; i++)中杰妓,即圓括號(hào)之內(nèi)建立了一個(gè)隱藏的作用域验靡,這就可以解釋為什么:
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
然后每次迭代循環(huán)時(shí)都創(chuàng)建一個(gè)新變量,并以之前迭代中同名變量的值將其初始化怔锌。 這樣對(duì)于下面這個(gè)一段代碼:
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // 0
就相當(dāng)于
// 偽代碼
(let i = 0) {
funcs[0] = function() {
console.log(i)
};
}
(let i = 1) {
funcs[1] = function() {
console.log(i)
};
}
(let i = 2) {
funcs[2] = function() {
console.log(i)
};
};
當(dāng)執(zhí)行函數(shù)的時(shí)候亚情,根據(jù)詞法作用域就可以找到正確的值裳瘪, 其實(shí)你也可以理解為let聲明模仿了閉包的做法來(lái)簡(jiǎn)化了循環(huán)過(guò)程。
循環(huán)中的let 和 const
如果我們把let 改成 const 呢
var funcs = [];
for (const i = 0; i < 10; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // Uncaught TypeError: Assignment to constant variable.
結(jié)果會(huì)報(bào)錯(cuò),因?yàn)殡m然每次都創(chuàng)建一個(gè)新的變量,然而我們卻在迭代中嘗試修改const的值,所以最終會(huì)報(bào)錯(cuò)溢十。
for in循環(huán)
var funcs = [], object = {a: 1, b: 1, c: 1};
for (var key in object) {
funcs.push(function(){
console.log(key)
});
}
funcs[0]() // 'c'
如果把var 改成let 或const
都輸出‘c’, 為什么這里改成const不會(huì)報(bào)錯(cuò)乌庶, 因?yàn)樵趂or in循環(huán)中,每次迭代不會(huì)修改已有的綁定,而是會(huì)創(chuàng)建一個(gè)新的綁定
balel
在babel中是如何編譯let和const的呢, 我們來(lái)看看編譯后的代碼:
let value = 1;
編譯為:
var value = 1;
我們可以看到babel直接將let編譯成var, 如果是這樣的話,那么看下面例子
if (false) {
let value = 1;
}
console.log(value); // Uncaught ReferenceError: value is not defined
如果還是直接編譯成var, 打印結(jié)果肯定是undefined, 然而babel很聰明編譯成了:
if (false) {
var _value = 1;
}
console.log(value);
在直觀寫個(gè)例子
let value = 1;
{
let value = 2;
}
value = 3;
編譯成:
var value = 1;
{
var _value = 2;
}
value = 3;
本質(zhì)是一樣的交排, 就是改變變量名,是內(nèi)外層的變量名稱不一樣。
那const的修改值時(shí)報(bào)錯(cuò),以及重復(fù)聲明報(bào)錯(cuò)是怎么實(shí)現(xiàn)的呢钱雷, 其實(shí)就是在編譯的時(shí)候直接給你報(bào)錯(cuò)...
那循環(huán)中的let聲明呢
var funcs = [];
for (let i = 0; i < 10; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // 0
babel巧妙的編譯成了
var funcs = [];
var _loop = function _loop(i) {
funcs[i] = function () {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
funcs[0](); // 0
最佳實(shí)踐
在我們開發(fā)的時(shí)候钞支,可能認(rèn)為應(yīng)該默認(rèn)使用let而不是var, 這種情況下撼嗓,對(duì)于需要寫保護(hù)的變量要使用const. 然而另一種做法日益普及: 默認(rèn)使用const, 只有當(dāng)確實(shí)需要改變變量的值的時(shí)候才使用let. 這是因?yàn)榇蟛糠值淖兞康闹翟诔跏蓟蟛粦?yīng)再改變礁遣,而預(yù)料之外的變量的改變是很多bug的源頭浅碾。
參考https://github.com/mqyqingfeng/Blog/issues/82
面試題:
var和let/const的區(qū)別
1.塊級(jí)作用域
2.不存在變量提升
3.暫時(shí)性死區(qū)
4.不可重復(fù)聲明
5.let const聲明的全局變量不會(huì)掛在頂層對(duì)象下面
const 需注意倆點(diǎn):
- const 聲明之后必須馬上賦值根暑,否則會(huì)報(bào)錯(cuò)
2.const簡(jiǎn)單類型一旦聲明就不能再更改,復(fù)雜類型(數(shù)組缰犁,對(duì)象等)指針指向的地址不能更改淳地,但是內(nèi)部數(shù)據(jù)可以更改颇象。