新手秒懂 - 高逼格解釋變量提高

做者廢話

最近爲了將來去大城市面試特地從新穩固基礎知識javascript

面試多了也會發現,不少都會涉及到平時工做中不去關心的問題, 接來下會不定時地像一樣的朋友們分享日常工做中不會接觸卻又常被問到的面試知識點前端

變量提高?

這個問題試問剛畢業的前端小白都能侃侃而談,咱們以兩道很常見的又很基礎的面試題一步一步揭開它的面紗。(做爲初學者的我常常搞混)java

function fun () {}
    var fun = 'fuck bitch'
    console.log(fun) //???
複製代碼
console.log(fun) //???
    function fun () {}
    var fun = 'fuck bitch'
複製代碼

大佬都會說,太簡單了。git

  1. 首先,第一題輸出的是funck bitch,這不涉及變量提高,只是同名的變量產生了覆蓋。(注意: 這發生在執行階段)
  2. 而後,第二道題, 輸出ƒ () {} , 具體的緣由,會談到你們都知道的變量提高知識

咱們習慣將 var a = 2; 看做一個聲明,而實際上 JavaScript 引擎並不這麼認爲。它將 var a 和 a = 2 看成兩個單獨的聲明,第一個是編譯階段的任務,而第二個則是執行階段的任務。github

這意味着不管做用域中的聲明出如今什麼地方,都將在代碼自己被執行前 首先 進行處理。 能夠將這個過程形象地想象成全部的聲明(變量和函數)都會被「移動」到各自做用域的最頂端,這個過程被稱爲提高。面試

聲明自己會被提高,而包括函數表達式的賦值在內的賦值操做並不會提高。瀏覽器

參考地址函數

如此能夠解釋 fun 沒有報錯的緣由,但不少跟我同樣的初學者就會問爲什麼輸出 ƒ () {} 而不是 undefined 或者 fuck bitchpost

對於這樣的問題,按照我以前的理解。在我眼裏,執行代碼其實就是ui

function fun () {}
console.log(fun) // --> fun(){}
fun = 'fuck bitch'
複製代碼

但若是問我爲啥會是這樣的,我就說不出來個因此然了 (果真仍是太菜了。。)

爲什麼會產生變量提高??

不知有沒有同窗想過這樣的問題,原本通常只是爲了應付面試而去瞄一眼說出個因此然就能夠的。但做爲要成爲將來資深的禿頭披風大佬,這樣作是遠遠不夠的。

這就涉及到javascript語言中執行上下文之變量對象的知識了

image

當 JavaScript 代碼執行一段可執行代碼(executable code)時,會建立對應的執行上下文(execution context)。

image

一個執行上下文的生命週期能夠分爲兩個階段。

  1. 建立階段

在這個階段中,執行上下文會分別建立變量對象,創建做用域鏈,以及肯定this的指向。

  1. 代碼執行階段

建立完成以後,就會開始執行代碼,這個時候,會完成變量賦值,函數引用,以及執行其餘代碼。

變量對象(Variable Object)

變量對象的建立,依次經歷瞭如下幾個過程。

  1. 創建arguments對象。檢查當前上下文中的參數,創建該對象下的屬性與屬性值。
  2. 檢查當前上下文的函數聲明,也就是使用function關鍵字聲明的函數。在變量對象中以函數名創建一個屬性,屬性值爲指向該函數所在內存地址的引用。若是函數名的屬性已經存在,那麼該屬性將會被新的引用所覆蓋。(常說的函數優先被提高, 且同名會產生覆蓋)
  3. 檢查當前上下文中的變量聲明(未聲明的會報錯 not defined),每找到一個變量聲明,就在變量對象中以變量名創建一個屬性,屬性值爲undefined。若是該變量名的屬性已經存在,爲了防止同名的函數被修改成undefined,則會 直接跳過 (注意: 跳過是在建立階段,跟第一題相對應,不要搞混),原屬性值 不會被修改

對照兩道題,結合描述,咱們就能夠了解到具體執行原理:

function fun () {}
    var fun = 'fuck bitch'
    console.log(fun) // 'fuck bitch'
複製代碼

由於 fun = 'fuck bitch' 是在執行上下文的執行過程當中運行的,而不會產生覆蓋,跳過操做是在建立階段,所以輸出結果天然會是fuck bitch

而第二道題

console.log(fun) // fun () {}
    function fun () {}
    var fun = 'fuck bitch'
複製代碼

由於 function 是優先被提高, 而接下來的變量 fun 由於同名而不會在建立階段產生覆蓋,因此輸出 fun () {}。 具體以下圖相同。

// 上例的執行順序爲

// 首先將全部函數聲明放入變量對象中
function fun () {}

// 其次將全部變量聲明放入變量對象中,可是由於fun已經存在同名函數,所以此時會跳過undefined的賦值
// var fun = undefined;

// 而後開始執行階段代碼的執行
console.log(fun); // function fun
fun = 'fuck bitch';
複製代碼

這樣一解釋,是否是以爲本身逼格瞬間上升了一個檔次 ?!

深刻拓展

還不用太興奮,咱們爲了加深理解,換個例子繼續一步一步來詳細地講。

function test() {
    console.log(a);
    console.log(foo());

    var a = 1;
    function foo() {
        return 2;
    }
}

test();
複製代碼

VO AND AO

在函數上下文, 咱們使用活動對象(activation object, AO)來表示變量對象。

未進入執行階段以前,變量對象(VO)中的屬性都不能訪問!可是進入執行階段以後,變量對象(VO)轉變爲了活動對象(AO),裏面的屬性都能被訪問了,而後開始進行執行階段的操做。

它們其實都是同一個對象,只是處於執行上下文的不一樣生命週期。

按照上例:
// 建立過程
testEC = {
    // 變量對象
    VO: {},
    scopeChain: {}
}

AO = {
    arguments: {...},  //注:在瀏覽器的展現中,函數的參數可能並非放在arguments對象中,這裏爲了方便理解,我作了這樣的處理
    foo: <foo reference> // 表示foo的地址引用 a: undefined } 複製代碼
// 執行階段
VO ->  AO   // Activation Object
AO = {
    arguments: {...},
    foo: <foo reference>, a: 1, this: Window } 複製代碼

按此來講, 上例的執行順序應該是:

function test() {
    function foo() {
        return 2;
    }
    var a = undefined;
    console.log(a);
    console.log(foo());
    a = 1;
}
test();
複製代碼

接下來增長難度:

// demo2
function test() {
    console.log(1, foo);
    console.log(2, bar);

    var foo = 'Hello';
    console.log(3, foo);
    var bar = function () {
        return 'world';
    }

    function foo() {
        return 'hello';
    }
}

test(); // ??? 
複製代碼

可轉換爲

// demo2
function test() {
    function foo() {
        return 'hello';
    }
    // 其次將全部變量聲明放入變量對象中,可是由於foo已經存在同名函數,所以此時會跳過undefined的賦值
    // var foo = undefined;
    
    var bar = undefined;
    
    console.log(1, foo);
    console.log(2, bar);

    var foo = 'Hello';
    
    console.log(3, foo);
    
    bar = function () {
        return 'world';
    }
}

test(); 
// 1 ƒ foo() {
// return 'hello';
// }
// 2 undefined
// 3 "Hello"
複製代碼

到此,即是我想向你們分享的內容,本小白菜雞打算明年年初向上海發起進攻,也但願能有個好的結果,努力,奮鬥。💪💪

參考文獻

變量對象詳解

深刻之變量對象

相關文章
相關標籤/搜索