淺談let和var的區別

DobbyKim發表於2019-05-12

淺談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 究竟有哪些不同呢?

  1. var的作用域是函式,而let的作用域則是塊
  2. var有變數提升,而let則沒有
  3. var可以重複定義,但是在一個scope內,重複定義也只是那一個變數,而重複定義let變數則會引起TypeError錯誤

如有錯誤,還請指出,不甚感激~

相關文章