用預編譯去理解函式宣告提升和變數宣告提升

快手崛起發表於2018-10-25

在我們前端筆試的過程中幾乎都會遇到函式宣告提升或者變數宣告提升的題目,是不是很多人知道這個知識點但是分析的時候還是會出現不同程度的錯誤呢,接下來跟我一步步走下去,讓大家以後再也不用擔心這類題目帶來的困擾。

1、js執行三部曲

我們必須要了解javascript有2個特點,單執行緒和解釋性語言,可以理解解釋性語言是翻譯一句執行一句,那麼在js執行時是經過以下三步:

  • 語法分析

  • 預編譯

  • 解釋執行

      說明:語法分析就是通篇掃描一遍是否有語法錯誤,沒有錯誤進行預編譯階段,然後進行解釋執行。
    複製程式碼

2、預編譯前奏

  • imply global 暗示全域性變數: 即任何變數,如果變數未經宣告就賦值,此變數就為全域性物件所有;
  • 一切宣告的全域性變數,全是window的屬性。

eg: 這裡只是簡要說明下以上亮點。
console.log(window.a) 
console.log(window.b)
function testGlobal(){
    var a = b = 123;
}
testGlobal()
console.log(b)
console.log(a)

eg: 按照我們思維解釋一行編譯一行,沒有任何問題。
function test(){
    console.log("test")
}
test()  // test

eg: 按照我們思維解釋一行編譯一行,應該報錯呀,為什麼能正常列印出結果呢。
test1() // test1
function test1(){
    console.log("test1")
}

eg:
var test3 = 5
console.log(test3) // 5

eg: 為什麼能輸出undefined
console.log(test4) //undefined
var test4 = 6

eg: 為啥這樣就報錯。
console.log(test5) // error

複製程式碼

經過以上的例子,很多人會快速的總結兩句話:

  • 1,函式宣告整體提升、
  • 2,變數的宣告提升。

這2句正確但是不能解決我們所有問題,不多說,用例子說話:


eg: 這裡我就不寫答案了,自己可以試一試是否掌握。
console.log(a);
function a(a){
    console.log(a);
    var a = 123;
    var a = function(){};
    console.log(a);
}
a(123);
var a = 123;

eg: 這裡我就不寫答案了,自己可以試一試是否掌握。
console.log(a);
function a(a){
    console.log(a);
    a = 123;
    function a(){};
    console.log(a);
}
a(123);
var a = 123;

複製程式碼

如果感覺有點暈乎,那麼我們瞭解預編譯則可以輕鬆解答問題。

3、預編譯

記住預編譯發生在函式執行的前一刻,我們記住以下四部曲即可:

  • 建立AO物件;
  • 找形參和變數宣告,將變數和形參作為AO物件的屬性名,值為undefined;
  • 將實參值和形參統一;
  • 在函式體裡面找到函式宣告,值賦予函式體。

那麼有人會問了,函式執行是這樣,那麼全域性定義的變數宣告和函式宣告會是怎樣呢,其實一樣,不同的就是第一步,全域性建立GO物件,GO也就是等於window。


eg: 我們先看函式執行
function fn(a){
    console.log(a);
    console.log(b);
    console.log(c);
    var a = 123;
    function a(){};
    console.log(a);
    var b = function(){};
    console.log(b);
    function c(){};
}
fn(111);

求解:對應以上步驟來
第1步: 建立AO物件
    AO{

    }
第2步:  找形參和變數宣告,將變數和形參作為AO物件的屬性名,值為undefined,如果形參和變數宣告或者函式宣告有同名的則寫一個就好。
    AO{
        a: undefined,
        b: undefined,
        c: undefined
    }
第3步: 將實參值和形參統一;
    AO{
        a: 111,
        b: undefined,
        c: undefined
    }
第4步: 在函式體裡面找到函式宣告,值賦予函式體。
    AO{
        a: function a(){},
        b: undefined,
        c: function c(){}
    }

第5步: 在按照順序執行,提升的和賦值過的我們就不用看了。
    fn(111);
    console.log(a)       // function a(){}   a ==> AO.a
    console.log(b)       // undefined
    console.log(c)       // function c(){}
    a =123               // 這裡只剩下賦值  A0.a = 123
    console.log(a)       // 123
    b = function(){};    // AO.b = function(){}
    console.log(b)       // function(){}

複製程式碼

總結說下第五步,這就是剩下執行的語句了,為啥變數宣告和函式宣告都沒有了呢,就是前面4步已經執行了,這下是不是有點恍然大悟,原來如此簡單,我們繼續多來幾個例子來說明,也便於增加記憶。


eg:
function fn1(a,b){
    console.log(a);
    console.log(b);
    var b =234;
    a = 123;
    console.log(a);
    function a(){};
    var a;
    b = 456;
    var b = function(){}
    console.log(a);
    console.log(b);
}
fn1(222);
按照預編譯步驟來解:
第1步: 
    AO{} 
第2步: 
    AO{
        a: undefined,
        b: undefined,
    }
第3步: 
    AO{
        a: 222,
        b: undefined,
    }
第4步: 
    AO{
        a: function a(){},
        b: undefined,
    } 
第5步:  解釋執行
    fn1(222);
    console.log(a)    // function a(){}    
    cosnole.log(b)    // undefined
    b =  234;         // AO.b = 234
    a = 123           // AO.a = 123
    console.log(a)    // 123
    b = 456;          // AO.b = 456
    b = function(){}  // Ao.b = function(){}
    console.log(a);   // 123
    console.log(b);   // function(){}

複製程式碼

現在經過以上2個案例是不是基本瞭解預編譯,是不是就不怕函式宣告提升和變數宣告提升呢,有人說了,要是全域性變數宣告和有全域性函式宣告又有函式體內函式宣告順序又是怎麼樣的呢?

記住全域性生成GO物件,函式執行體生成AO物件。其他規則一樣,繼續來例子說明:


eg:
console.log(global)
function global(){
    console.log(global)
    global = 200
    console.log(global)
    var global = 300
}
global()
console.log(global);
global = 100;
var global;
console.log(global);


第1步: 
    GO{}
第2步:  
    GO{
        global: undefined     
    }  
第3步:, 沒有    
第4步:  
    GO{
        global: global(){ ...} 
    }  
第5步:解釋執行 
    console.log(global)   //  global(){ ...} 
    接下來要執行函式,那麼執行函式之前要走4步       
第1步: 
    AO{}

第2步: 
    AO{
        global: undefined
    }
第3步: 沒有
第4步: 沒有

第5步:解釋執行 
    global()
    console.log(global)  // undefined  注意:AO物件找到則值為AO的屬性值,沒有則去GO物件裡面去找
    global = 200         // AO.global = 200
    console.log(global)  // 200
    global = 300         // AO.global = 300
    global = 100;        // GO.global = 100
    console.log(global)  // 100  這裡是全域性的global

eg: 這一題有了if,其實預編譯我只管找你宣告,其他不管,那麼又和以上步驟相同,還有就是沒有宣告就賦值則掛在GO物件上
function test(){
    console.log(a);    // undefined  AO物件沒有,則到GO物件中找
    console.log(b);    // undefined
    if(a){             
        var b = 100;   // 不執行b的賦值
    }
    console.log(b);    // undefined
    c = 234;
    console.log(c)     //234
}
var a ;
test();
a = 10;
console.log(c)  //234

解答,這裡我就簡要的寫最終兩個物件的值:
GO{ 
    a: 10
    c: 234
}
AO{
    b: undefined,

}
所以答案很明顯.
複製程式碼

4、總結

經過以上總結,那我們是不是可以找到規律:

  • 列印變數值前面有賦值,則該值為賦值的值。
  • 列印變數值之前沒有賦值,有函式宣告則值函式宣告,沒有再看形參,再沒有看變數宣告,從AO找到GO。說白了就是4步曲的倒序。

原文網址:weblogger.club/2018/10/19/…

相關文章