面試題之 let 簡析

爲何會有 let ?

ES6 已經出現很長時間了,可是做爲一個初學者,仍然要仔細深刻的理解這些點,接下來我會寫一個 ES6 語法系列,深刻講解 ES6 語法產生的背景與用法,但願能給你們帶來幫助。javascript

塊級做用域

你們都清楚,在 let 聲明方式出現以前,咱們聲明一個變量只能經過 var 來定義。html

var a = 1;
var b = 'zhangsan';
var c = {name: 1, age: 2};
var d = function(){}
var e = [];
複製代碼

一切都很正常,直到有一天,咱們寫出了這樣的代碼:java

for(var i = 0; i < 10; i++){
    setTimeout(function(){
        console.log(i);
    });
}
複製代碼

咱們指望使用這種代碼,獲得以下結果:閉包

0
1
2
3
4
5
6
7
8
9
複製代碼

但事實卻很打臉,獲得的結果以下:函數

這和咱們的指望結果不太同樣,爲何會獲得這樣的結果呢? 在此能夠說明一下,雖然有些跑題。ui

緣由有二:spa

一個緣由是: var 定義的變量不受塊級做用域的限制。code

另外一個緣由是:JavaScript 引擎的事件循環機制在起做用。for循環的同步任務執行完畢以後,纔會將從事件隊列中取出回調函數,放到調用棧中執行:即setTimeout的回調函數。因此當同步任務執行完以後, i 的值已經變爲了 10,此時,10 個定時器的回調開始執行,打印出 10 個 10。cdn

那麼,聰明的同窗開始想辦法了,利用 JS 中的閉包特性,實現指望的結果:htm

for(var i = 0; i < 10; i++){
    (function(t){
		setTimeout(function(){
        	console.log(t);
    	});
    })(i);
}
複製代碼

能夠看到,代碼不易理解,書寫也很麻煩。

因而 ES6 中新出現了 let 聲明方式, 經過 let 聲明的變量有了塊級做用域的限制。 舉個例子,先從 var 聲明開始:

{
    var a = 1;
}
console.log(a);
複製代碼

因爲 var 沒有塊級做用域的約束,因此咱們在塊級做用域之外訪問 a 變量的話,仍然是可以訪問到的。所以,上面的定時器例子會打印出 10 個 10。

接下來,咱們將 var 改成 let 進行聲明:

{
    let a = 1;
}
console.log(a);
複製代碼

你們能夠猜想一下輸出結果是什麼?

事實上會報錯的:

緣由在於,按照 let 的聲明特色, let 定義的變量只能在聲明時所在的做用域中訪問到,因此 a 被限制在了塊級做用域中,在塊級做用域外訪問 a 的話,因爲外層做用域並未定義 a 變量,因此會報上述錯誤。

既然 let 有了塊級做用域的約束,咱們就能夠用 let 來改寫上面的定時器例子:

for(let i = 0; i < 10; i++){
    setTimeout(function(){
        console.log(i);
    });
}
複製代碼

能夠看到,使用let 咱們就可以正常將 i 值約束在每個循環塊做用域中了,這比 ES5 中利用閉包要容易理解多了。

重複聲明的隱患

之前咱們用 var 聲明變量的時候,能夠重複進行同名變量的聲明,好比

var a = 1;
var a = 2;
var a = 3;
console.log(a);
複製代碼

以上代碼,沒有報錯,而且 a 的值獲得了篡改,以最後一次賦值爲準。

你們可能以爲沒有問題。

咱們再舉一個例子:

假設 A、B、C 三個同窗須要共同完成一個頁面功能。

A同窗寫了一個 JS 文件 A.js,A同窗再這個文件中定義了一個name變量,賦值爲章三,他想在頁面上將這個名字打印出來。

var name = '章三'複製代碼

B同窗寫了另外一個 JS 文件 B.js,也定義了一個變量,也叫 name,賦值爲 '李四',他也想在頁面上將這個名字打印出來。

var name = '李四';
複製代碼

A 同窗告訴 C 同窗,取出 name 屬性,打印出來就能夠了。

B 同窗告訴 C 同窗,取出 name 屬性,打印出來就能夠了。

惋惜 C 同窗不是一個細心的同窗,他沒有意識到兩個變量重名了,因而他寫了一個 html 文件,引入了 A.js 和 B.js,而後將 name 屬性打印出來。

<script src="./A.js"></script>
<script src="./B.js"></script>

<body>
    <div id="name"> </div>
    <script> document.querySelector('#name').innerHTML = name; </script>
</body>
複製代碼

而後 C 同窗將頁面發給 A 同窗和 B 同窗,讓他們看一下結果對不對。

結果 A 同窗一看,發現打印出來的名字不是章三,而是 李四,他就怒氣衝衝地去質問 C 同窗,C同窗說,我就是按照你告訴個人方式去打印的呀。

故事進行到這裏,矛盾出現了:

同一個做用域下定義多個重名變量,JavaScript 引擎不會報錯,可是會爲程序的正確性帶來隱患。

幸運的是,let 的出現很好地解決了這個問題:

同一個做用域下,let 聲明的變量不能和已經聲明的變量重名,不然引擎會報錯。

let a = 1;
let a = 2;
複製代碼

或者

var a = 1;
let a = 2;
複製代碼

因此,let 爲咱們帶來了另外一個好處,防止咱們定義重名變量。

變量聲明再也不提高

仍然以一段代碼爲例:

console.log(a);
var a = 1;
複製代碼

你們猜想一下,輸出結果是什麼?

程序運行不會報錯,可是輸出結果不是咱們指望的。

輸出結果是 undefined。

因此,這會帶來一個問題,咱們在聲明一個變量以前,萬一使用了這個變量,就會獲得意料以外的結果,進而形成程序運行錯誤,若是能有一種機制可以強制咱們在使用變量以前,必須先聲明該變量就行了。

這就是 let 的第三個特色:使用一個 let 聲明的變量以前,必須先聲明。

console.log(a);
let a = 1;
複製代碼

如上代碼,a 變量的聲明放在了使用以後,咱們看下運行結果:

編譯器給出了報錯提示,這促使咱們在早期就能發現問題。

結語

以上就是 ES6 的 let 使用總結,雖然是一個很小的點,可是咱們也要認清它出現的背景,爲了解決什麼問題而生。

以後,會爲你們帶來 ES6 其餘語法的剖析,敬請期待~~~

相關文章
相關標籤/搜索