JavaScript變量提高運行機制

JavaScript的工做原理是,先解析代碼,獲取全部聲明的變量或者函數,而後運行。這形成的結果就是全部聲明的變量或者函數都被提高到代碼的頭部,這叫作聲明提示提高。bash

先看一個換湯不換藥的經典例子:這個例子你們其實都知道發生了變量提高致使\color{red}{console.log(a)};結果是undefined。問題來了,這個提高的過程在哪裏發送?怎麼發生?函數

console.log(a);  // undefined
var a = 1;
b()              // 1
function b() {
    console.log(a)
}
複製代碼

1、JavaScript的執行過程

1. JavaScript在執行以前會經歷一個編的過程(其餘術語:建立、預解釋過程)。

1.1 宏觀的角度

  • 建立\color{red}{Scope chain}(包含變量對象,父級做用域上下文的變量對象)。
  • 建立\color{red}{variableObject}(參數、內部變量、函數聲明)。
  • 設置\color{red}{this}
PS:變量對象的建立過程
  • 根據函數的參數,建立並初始化argument object。
  • 掃描函數內部的代碼,查找函數聲明。對於全部找到的函數名和函數引用,都會放到變量對象中。若是以前已經存在同名的函數,會進行覆蓋。
  • 掃描函數內部的代碼,找到變量聲明,對於全部找到的變量申明,都放到變量對象中,並初始化爲undefined,變量名稱跟已經聲明的參數或者是函數相同,就會被忽略,不會干擾到以前的聲明。

1.2 微觀的角度

  • \color{red}{分詞/詞法分析},這個過程就是將字符串分解爲有意義的代碼塊,這些代碼塊咱們稱爲詞法單元。
  • \color{red}{解析/語法分析},這個過程是將詞法單位抽象爲抽象語法樹AST。
  • \color{red}{代碼生成}。將AST生成能夠執行的代碼。

2. 編譯完以後會進行一個執行階段。

2.1 執行:變量的值、函數的引用、執行代碼。

一、\color{red}{變量的提高發生在編譯的代碼生成階段析},例如在代碼中var a,編譯器會詢問做用域中是否有一個該名稱的變量存在。若是存在,就繼續往下編譯,若是不存在就會在當前做用域申明一個新的變量。網站

二、運行階段,引擎發現代碼a = 1的時候,首先會查詢當前做用域是不是否有一個名爲a的變量,若是存在就進行 賦值,\color{red}{若是沒有就向上級執行,這個查詢過程是在做用域鏈中完成}。大多數狀況下,編譯發生在代碼執行以前的幾微秒(甚至更短)。ui

2、ES6 let、const

ES新增的變量聲明語法let與const。咱們用let做爲例子。this

function f() {
  console.log(a);
  let a = 2;
}
f(); // ReferenceError: a is not defined
複製代碼

這段代碼直接報錯a is not defined,let和const擁有相似的特徵,阻止了變量提高,當執行console.log(a)的時候變量沒有定義,因此報錯了。 在阮一峯老師的網站也寫到let和const都是不存在變量提高的。以下 lua

\color{red}{可是在最近的閱讀中發現,有人對let真的不存在變量提高提出了疑問。},對此我也產生的疑問,發如今不一樣的權威機構有着不同的解釋。spa

  • MDN中寫到:In ECMAScript 2015, let do not support Variable Hoisting, which means the declarations made using "let", do not move to the top of the execution block.

在MDN中認爲let不存在變量提高3d

  • ECMA-262-13.3.1 Let and Const Declarations寫到: 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.

這說明即便是 block 最後一行的 let 聲明,也會影響 block 的第一行。這就是提高(hoisting)code

  • ECMA-262: 8.2.1.2 Runtime Semantics: EvalDeclarationInstantiation( body, varEnv, lexEnv, strict)寫到: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts.

這句話也間接的證實 let hoisting 的存在。\color{red}{在 ECMAScript 2015中, let 也會提高到語句塊的頂部。可是,在這個語句塊中,在變量聲明以前引用這個變量會致使一個 ReferenceError的結果。}cdn

那其實你們會有疑問,爲何上面的代碼會報錯。其實這並非因爲變量不提示致使的,而是因爲TDZ(臨時性死區)致使的。

在舉個例子:
{
    a = 2;
    let a;
}
複製代碼
這段代碼能夠解釋爲:
{
    let a;// 變量提高
    "start TDZ"
    a = 2; // 這裏在TDZ中間,因此會致使a = 2 報錯
    a;
    "end TDZ"
}
複製代碼

因此破案了:let是不存在變量提高。它「變量提高的行爲」,是因爲TDZ致使的。

so...總結一下

  • let 聲明會提高到塊頂部
  • 從塊頂部到該變量的初始化語句,這塊區域叫作 TDZ(臨時死區)
  • 若是你在 TDZ 內使用該變量,JS 就會報錯,注意TDZ 跟 hoisting不等價。
相關文章
相關標籤/搜索