溫故而知新:JS 變量提高與時間死區

開始執行腳本時,執行腳本的第一步是編譯代碼,而後再開始執行代碼,如圖javascript

clipboard.png

另外,在編譯優化方面來講,最開始時也並非所有編譯好腳本,而是當函數執行時,纔會先編譯,再執行腳本,如圖java

clipboard.png

  • 編譯階段:經歷了詞法分析,語法分析生成AST,以及代碼生成。而且在此階段,它只會掃描而且抽出環境中的聲明變量,聲明函數以便準備分配內存,全部的函數聲明和變量聲明都會被添加到名爲Lexical Environment的JavaScript內部數據結構內的內存中。所以,它們能夠在源代碼中實際聲明以前使用。可是,Javascript只會存儲函數聲明和變量聲明在內存,並不會存儲他們的值
  • 執行階段:給變量x賦值,首先詢問內存你這有變量x嗎,若是有,則給變量x賦值,若是沒有則建立變量x而且給它賦值。

變量提高

以下圖,左邊灰色塊區域,是演示函數執行前的編譯階段,先抽出全部聲明變量和聲明函數,並進行內存分配。而後再開始執行代碼,在執行第一行代碼的時候,如果變量a存在於內存中,則直接給變量a賦值。而執行到第二行時,變量b並無在內存中,則會建立變量b並給它賦值。數據結構

clipboard.png

Lexical enviroment是一種包含標識符變量映射的數據結構app

LexicalEnviroment = {
  Identifier: <value>,
  Indentifier: <function object>
}

簡而言之,Lexical enviroment就是程序執行過程當中變量和函數存在的地方。函數

let,const變量

console.log(a)
let a = 3;

輸出優化

ReferenceError: a is not defined

因此let和const變量並不會被提高嗎?this

這個答案會比較複雜。全部的聲明(function, var, let, const and class)在JavaScript中都會被提高,然而var聲明被undefined值初始化,可是letconst聲明的值仍然未被初始化。spa

它們僅僅只在Javascript引擎運行期間它們的詞法綁定被執行在纔會被初始化。這意味着引擎在源代碼中聲明它的位置計算其值以前,你沒法訪問該變量。這就是咱們所說的時間死區,即變量建立和初始化之間的時間,咱們沒法訪問該變量。code

若是JavaScript引擎仍然沒法在聲明它們的行中找到let或者const的值,它將爲它們分配undefined值或返回錯誤值(在const的狀況下會返回錯誤值)。blog

clipboard.png

6a9a50532bf60f5fac6b3c.png](evernotecid://F2BCA3B5-CC5A-4EB3-BD61-DD865800F342/appyinxiangcom/10369121/ENResource/p1163)

let a;
console.log(a); // outputs undefined
a = 5;

在編譯階段,JavaScript引擎遇到變量a並將它存儲在lexical enviroment,可是由於它是一個let變量,因此引擎不會爲它初始化任何值。因此,在編譯階段,lexical enviroment看起來像下面這樣。

// 編譯階段
lexicalEnvironment = {
  a: <uninitialized>
}

如今若是咱們嘗試在聲明它以前訪問該變量,JavaScript引擎將會嘗試從詞法環境中拿到這個變量的值,由於這個變量未被初始化,它將拋出一個引用錯誤。

在執行期間,當引擎到達了變量聲明的行,它將試圖執行它的綁定,由於該變量沒有與之關聯的值,所以它將爲其賦值爲unedfined

// 執行階段
lexicalEnviroment = {
  a: undefined
}

以後,undefined將會被打印到控制檯,而後將值5賦值給變量a,lexical enviroment中變量a的值也會從undefined更新爲5

functionn foo() {
  console.log(a)
}

let a = 20;

foo();
function foo() {
  console.log(a): // ReferenceError: a is not defined
}
foo();
let a = 20;

clipboard.png

Class Declaration

就像letconst聲明同樣,class在JavaScript中也會被提高,而且和let,const同樣,知道執行以前,它們都會保持uninitialized。所以它們一樣會受到Temporal Deal Zone(時間死區)的影響。例如

let peter = new Person('Peter', 25); // ReferenceError: Person is not defined

console.log(peter);

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

所以要訪問class,必須先聲明它

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

let peter = new Person('Peter', 25); 
console.log(peter);
// Person { name: 'Peter', age: 25 }

因此在編譯階段,上面代碼的lexical environment(詞法環境)將以下所示:

lexicalEnvironment = {
  Person: <uninitialized>
}

當引擎執行class聲明時,它將使用值初始化類。

lexicalEnvironment = {
  Person: <Person object>
}

提高Class Expressions

let peter = new Person('Peter', 25);
console.log(peter);
let Person = class {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

clipboard.png

let peter = new Person('Peter', 25); 
console.log(peter);
var Person = class {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

clipboard.png

因此如今咱們知道在提高過程當中咱們的代碼並無被JavaScript引擎實際移動。正確理解提高機制將有助於避免因變量提高而產生的任何將來錯誤和混亂。爲了不像未定義的變量或引用錯誤同樣可能產生的反作用,請始終嘗試將變量聲明在各自做用域的頂部,並始終嘗試在聲明變量時初始化變量。

Hoisting in Modern JavaScript — let, const, and var

相關文章
相關標籤/搜索