淺談let和var的區別
關於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迴圈這個塊使用時,則不會收到外部的一些影響。
總結
var 和 let 究竟有哪些不同呢?
- var的作用域是函式,而let的作用域則是塊
- var有變數提升,而let則沒有
- var可以重複定義,但是在一個scope內,重複定義也只是那一個變數,而重複定義let變數則會引起TypeError錯誤
如有錯誤,還請指出,不甚感激~