目錄html
1.序言es6
var、let 和 const 都是 JavaScript 中用來聲明變量的關鍵字,而且 let 和 const 關鍵字是在 ES6 中才新增的。既然都是用來聲明變量的,那它們之間有什麼區別呢?讓咱們來一探究竟。瀏覽器
2.var 與 let 的區別數據結構
(1)做用域ide
用 var 聲明的變量的做用域是它當前的執行上下文,即若是是在任何函數外面,則是全局執行上下文,若是在函數裏面,則是當前函數執行上下文。換句話說,var 聲明的變量的做用域只能是全局或者整個函數塊的。函數
而 let 聲明的變量的做用域則是它當前所處代碼塊,即它的做用域既能夠是全局或者整個函數塊,也能夠是 if、while、switch等用{}
限定的代碼塊。post
另外,var 和 let 的做用域規則都是同樣的,其聲明的變量只在其聲明的塊或子塊中可用。this
示例代碼:lua
function varTest() { var a = 1; { var a = 2; // 函數塊中,同一個變量 console.log(a); // 2 } console.log(a); // 2 } function letTest() { let a = 1; { let a = 2; // 代碼塊中,新的變量 console.log(a); // 2 } console.log(a); // 1 } varTest(); letTest();
從上述示例中能夠看出,let 聲明的變量的做用域能夠比 var 聲明的變量的做用域有更小的限定範圍,更具靈活。指針
(2)重複聲明
var 容許在同一做用域中重複聲明,而 let 不容許在同一做用域中重複聲明,不然將拋出異常。
var 相關示例代碼:
var a = 1; var a = 2; console.log(a) // 2 function test() { var a = 3; var a = 4; console.log(a) // 4 } test()
let 相關示例代碼:
if(false) { let a = 1; let a = 2; // SyntaxError: Identifier 'a' has already been declared }
switch(index) { case 0: let a = 1; break; default: let a = 2; // SyntaxError: Identifier 'a' has already been declared break; }
從上述示例中能夠看出,let 聲明的重複性檢查是發生在詞法分析階段,也就是在代碼正式開始執行以前就會進行檢查。
(3)綁定全局對象
var 在全局環境聲明變量,會在全局對象裏新建一個屬性,而 let 在全局環境聲明變量,則不會在全局對象裏新建一個屬性。
示例代碼:
var foo = 'global' let bar = 'global' console.log(this.foo) // global console.log(this.bar) // undefined
那這裏就一個疑問, let 在全局環境聲明變量不在全局對象的屬性中,那它是保存在哪的呢?
var foo = 'global' let bar = 'global' function test() {} console.dir(test)
在Chrome瀏覽器的控制檯中,經過執行上述代碼,查看 test 函數的做用域鏈,其結果如圖:
由上圖可知,let 在全局環境聲明變量 bar 保存在[[Scopes]][0]: Script
這個變量對象的屬性中,而[[Scopes]][1]: Global
就是咱們常說的全局對象。
(4)變量提高與暫存死區
var 聲明變量存在變量提高,如何理解變量提高呢?
在 JavaScript 代碼運行時,解釋執行全局代碼、調用函數或使用 eval 函數執行一個字符串表達式都會建立並進入一個新的執行環境,而這個執行環境被稱之爲執行上下文。所以執行上下文有三類:全局執行上下文、函數執行上下文、eval 函數執行上下文。
執行上下文能夠理解爲一個抽象的對象,以下圖:
Variable object:變量對象,用於存儲被定義在執行上下文中的變量 (variables) 和函數聲明 (function declarations) 。
Scope chain:做用域鏈,是一個對象列表 (list of objects) ,用以檢索上下文代碼中出現的標識符 (identifiers) 。
thisValue:this 指針,是一個與執行上下文相關的特殊對象,也被稱之爲上下文對象。
一個執行上下文的生命週期能夠分爲三個階段:建立、執行、釋放。以下圖:
而全部使用 var 聲明的變量都會在執行上下文的建立階段時做爲變量對象的屬性被建立並初始化,這樣才能保證在執行階段能經過標識符在變量對象裏找到對應變量進行賦值操做等。
而用 var 聲明的變量構建變量對象時進行的操做以下:
上述過程就是咱們所謂的「變量提高」,這也就能解釋爲何變量能夠在聲明以前使用,由於使用
是在執行階段,而在此以前的建立階段就已經將聲明的變量添加到了變量對象中,因此執行階段經過標識符能夠在變量對象中查找到,也就不會報錯。
示例代碼:
console.log(a) // undefined var a = 1; console.log(a) // 1
let 聲明變量存在暫存死區,如何理解暫存死區呢?
其實 let 也存在與 var 相似的「變量提高」過程,但與 var 不一樣的是其在執行上下文的建立階段,只會建立變量而不會被初始化(undefined),而且 ES6 規定了其初始化過程是在執行上下文的執行階段(即直到它們的定義被執行時才初始化),使用未被初始化的變量將會報錯。
let
andconst
declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in alet
declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.
在變量初始化前訪問該變量會致使 ReferenceError
,所以從進入做用域建立變量,到變量開始可被訪問的一段時間(過程),就稱爲暫存死區(Temporal Dead Zone)。
示例代碼 1:
console.log(bar); // undefined console.log(foo); // ReferenceError: foo is not defined var bar = 1; let foo = 2;
示例代碼 2:
var foo = 33; { let foo = (foo + 55); // ReferenceError: foo is not defined }
注:首先,須要分清變量的建立、初始化、賦值是三個不一樣的過程。另外,從 ES5 開始用詞法環境(Lexical Environment)替代了 ES3 中的變量對象(Variable object)來管理靜態做用域,但做用是相同的。爲了方便理解,上述講解中仍保留使用變量對象來進行描述。
小結
var 聲明的變量在執行上下文建立階段就會被「建立」和「初始化」,所以對於執行階段來講,能夠在聲明以前使用。
let 聲明的變量在執行上下文建立階段只會被「建立」而不會被「初始化」,所以對於執行階段來講,若是在其定義執行前使用,至關於使用了未被初始化的變量,會報錯。
3.let 與 const 異同
const 與 let 很相似,都具備上面提到的 let 的特性,惟一區別就在於 const 聲明的是一個只讀變量,聲明以後不容許改變其值。所以,const 一旦聲明必須初始化,不然會報錯。
示例代碼:
let a; const b = "constant" a = "variable" b = 'change' // TypeError: Assignment to constant variable
如何理解聲明以後不容許改變其值?
其實 const 其實保證的不是變量的值不變,而是保證變量指向的內存地址所保存的數據不容許改動(即棧內存在的值和地址)。
JavaScript 的數據類型分爲兩類:原始值類型和對象(Object類型)。
對於原始值類型(undefined、null、true/false、number、string),值就保存在變量指向的那個內存地址(在棧中),所以 const 聲明的原始值類型變量等同於常量。
對於對象類型(object,array,function等),變量指向的內存地址實際上是保存了一個指向實際數據的指針,因此 const 只能保證指針是不可修改的,至於指針指向的數據結構是沒法保證其不能被修改的(在堆中)。
示例代碼:
const obj = { value: 1 } obj.value = 2 console.log(obj) // { value: 2 } obj = {} // TypeError: Assignment to constant variable
4.參考
let - JavaScript - MDN - Mozilla
const - JavaScript - MDN - Mozilla