深刻理解JS:var、let、const的異同

目錄html

  • 序言
  • var 與 let 的區別
    • 做用域
    • 重複聲明
    • 綁定全局對象
    • 變量提高與暫存死區
  • let 與 const 異同
  • 參考

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 函數的做用域鏈,其結果如圖:

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 聲明的變量構建變量對象時進行的操做以下:

  • 由名稱和對應值(undefined)組成一個變量對象的屬性被建立(建立並初始化)
  • 若是變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性。

上述過程就是咱們所謂的「變量提高」,這也就能解釋爲何變量能夠在聲明以前使用,由於使用是在執行階段,而在此以前的建立階段就已經將聲明的變量添加到了變量對象中,因此執行階段經過標識符能夠在變量對象中查找到,也就不會報錯。

示例代碼:

console.log(a) // undefined

var a = 1;

console.log(a) // 1

let 聲明變量存在暫存死區,如何理解暫存死區呢?

其實 let 也存在與 var 相似的「變量提高」過程,但與 var 不一樣的是其在執行上下文的建立階段,只會建立變量而不會被初始化(undefined),而且 ES6 規定了其初始化過程是在執行上下文的執行階段(即直到它們的定義被執行時才初始化),使用未被初始化的變量將會報錯。

let and const 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 a let 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)來管理靜態做用域,但做用是相同的。爲了方便理解,上述講解中仍保留使用變量對象來進行描述。


小結

  1. var 聲明的變量在執行上下文建立階段就會被「建立」和「初始化」,所以對於執行階段來講,能夠在聲明以前使用。

  2. 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.參考

var - JavaScript | MDN

let - JavaScript - MDN - Mozilla

const - JavaScript - MDN - Mozilla

深刻理解JavaScript系列(12):變量對象(Variable Object)

ES6 let 與 const

詳解ES6暫存死區TDZ

嗨,你知道 let 和 const 嗎?

相關文章
相關標籤/搜索