淺談let和var的區別
首先來看一看MDN對於var的解釋:
1: 變數宣告,無論發生在何處,都在執行任何程式碼之前進行處理。(變數提升) 2: 宣告變數的作用域限制在其宣告位置的上下文中,而非宣告變數總是全域性的 3: 如果你重新宣告一個JavaScript變數,它將不會丟失其值 ... ... 複製程式碼
我只摘抄了一部分特性,但是這一部分特性就已經能夠讓我們明白var與let的區別了,在談var與let的區別之前,我們先簡單看一下var的這些特性。
var的變數提升及作用域
首先來看一道面試題:請問下面的程式碼會打印出什麼樣的結果?
var a = 99; f(); console.log(a); function f(){ console.log(a); var a = 10; console.log(a); } 複製程式碼
這道題的考點是作用域及變數提升,首先我們先將所有的變數進行提升:
var a; function f(){ var a; console.log(a); a = 10; console.log(a); } a = 99; f(); console.log(a); 複製程式碼
結果為:
undefined 10 99 複製程式碼
我們首先將全域性變數a與函式f以及函式內部的區域性變數a進行了變數提升,執行函式f時,由於函式f內部的第一個console.log列印的a在函式f這個作用域已經宣告過了,但還並未賦值所以首先會打印出來undefined;在函式f內部的第二個console.log列印的結果則是賦值以後的a,且這個a仍然是函式這個作用域中的a,所以打印出來的結果為 10;在 程式碼最後的console.log中的a則是當前作用域即全域性變數的a,這個a宣告過且賦值為99,所以最後一個結果是99。如果你完全理解了這道題目,那麼相信你自然會了解var的變數提升以及作用域。
一不小心就聲明瞭全域性變數
我們首先來看一個MDN的例子:
function x() { y = 1;// 在嚴格模式(strict mode)下會丟擲 ReferenceError 異常 var z = 2; } x(); console.log(y); // 打印出結果1 console.log(z); // ReferenceError: z 未在 x 外部宣告 複製程式碼
在函式x的內部,我們未對y變數進行任何宣告而是直接對其進行了賦值,這樣做,我們一不小心就聲明瞭一個全域性變數,我們的本意是在函式x的內部宣告一個區域性變數,但是在函式x的外部使用console.log也是能打印出來y的值,其實上面的程式碼相當於:
var y = 1; function x(){ var z; y = 1; z = 2; } x(); console.log(y); console.log(z); 複製程式碼
關於第三點解釋
3: 如果你重新宣告一個JavaScript變數,它將不會丟失其值 複製程式碼
關於第三點解釋,我們用自己的話來翻譯一下
var 可以重複對一個變數進行宣告,但是無論怎麼宣告它都是那個當前作用域的那個變數 複製程式碼
聽起來有些難懂,我們不妨看一下程式碼:
HTML中有: <div id=parent></div> =====================>我是分割線<===================== 在script標籤中: var parent = document.getElementById('parent'); 試問: console.log(parent); console.log(window.parent); 這兩個結果在瀏覽器的控制檯中分別打印出什麼? 複製程式碼
答案為:
打印出的結果均為:id為parent的div元素 複製程式碼
實際上,我們聲明瞭一個已經宣告過的全域性屬性,window.parent全域性屬性為:如果有父視窗,即返回父視窗;如果沒有則返回當前視窗。在本例中,我們對parent再次宣告,並且我們對宣告的變數進行了賦值,這也相當於對原有的全域性屬性parent進行了覆蓋。也就是說:
var a = 1; var a = 2; var a = 3; var a = 4; console.log(a); 複製程式碼
這段程式碼沒有語法錯誤,不過它相當於:
var a; a = 1; a = 2; a = 3; a = 4; console.log(a); 複製程式碼
關於let
我們還是先看一下MDN對於let的解釋:
1: let的作用域是塊,而var的作用域是函式 2: 一個作用域中用let重複定義一個變數將引起 TypeError ... ... 複製程式碼
let的解釋,我也只是摘抄了一部分,如果你想更加詳細地瞭解,可以搜尋MDN~~~
let的作用域
let
宣告的變數只在其宣告的塊或子塊中可用,這一點,與var
相似。二者之間最主要的區別在於var
宣告的變數的作用域是整個封閉函式。 示例如下:
{ var dobby = 666; } console.log(dobby); // 打印出666 { let kim = 666; } console.log(kim); // 報錯ReferenceError,kim is not defined 複製程式碼
MDN上的例子更加生動形象,我就借來用一下了 : -)
function varTest() { var x = 1; if (true) { var x = 2;// 同樣的變數! console.log(x);// 2 } console.log(x);// 2 } function letTest() { let x = 1; if (true) { let x = 2;// 不同的變數 console.log(x);// 2 } console.log(x);// 1 } 複製程式碼
從上面的例子可以看出來,let的作用域,我們回過頭再來看一看這個問題:
HTML中有: <div id=parent></div> =====================>我是分割線<===================== 在script標籤中: var parent = document.getElementById('parent'); 複製程式碼
如果我們非要使用parent這樣一個變數(當然不推薦,因為全域性屬性可恥!),讓它表示id為parent的div元素,並且我們希望全域性屬性window.parent不受影響,我們就可以使用let~
{ let parent = document.getElementById('parent'); console.log(parent); } console.log(window.parent); // 除此之外,也可以使用立即執行函式,因為再次強調var的作用域是函式 (function(){ var parent = document.getElementById('parent'); console.log(parent); }).call(); 複製程式碼
let沒有變數提升
與var不同,let沒有變數提升,沒有變數提升,沒有變數提升。還是借用一下MDN的好例子 :-)
function do_something() { console.log(bar); // undefined console.log(foo); // ReferenceError: foo is not defined var bar = 1; let foo = 2; } 複製程式碼
我們來看一下阮一峰的解釋:
ES6 明確規定,如果區塊中存在let和const命令,這個區塊對這些命令宣告的變數,從一開始就形成了封閉作用域。凡是在宣告之前就使用這些變數,就會報錯。 總之,在程式碼塊內,使用let命令宣告變數之前,該變數都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱 TDZ) 複製程式碼
其實簡單一句話概括就是:let不存在變數的提升,這樣是和var的區別之一
重複定義let變數會引起錯誤
直接上示例:
{ let dobby = 1; let dobby = 2;// TypeError thrown. } 複製程式碼
沒什麼好解釋的 :-)
一道面試題
for (var i = 0; i <10; i++) { setTimeout(function() { console.log(i); }, 1000); } 複製程式碼
與
for (let i = 0; i <10; i++) { setTimeout(function() { console.log(i); }, 1000); } 複製程式碼
兩個程式碼列印的結果是什麼?
這是一道已經被玩爛的面試題,因為我還沒有學習到JS的事件迴圈機制,如果真的讓我說出個之所以然來,我的確無法進行詳細的說明,但是在後續,我會繼續將這道題單獨寫一篇部落格,好好深入研究其中的機制與奧祕~
var 迴圈的結果為:10個10 let 迴圈的結果為:0,1,2,3,4,5,6,7,8,9 複製程式碼
其實拋開上面的諸多知識點,我們也可以簡單先進行一波分析,對於var迴圈來講,我們可以先這樣寫:
var i; for (i = 0; i <10; i++) { setTimeout(function() { console.log(i); }, 1000); } 複製程式碼
由於var的變數提升機制,我們實際上相當於聲明瞭一個全域性變數,再回顧下這個程式碼:
var a; a = 1; a = 2; a = 3; a = 4; console.log(a); 複製程式碼
在var迴圈中,其實我們列印的i 就是全域性的唯一的那個變數,所以會打印出10個10;而對於let的迴圈則不同,我們回顧一下let的作用域,當i在for迴圈這個塊使用時,則不會收到外部的一些影響。