JS進階系列-JS執行期上下文(一)

點贊再看,年薪百萬 本文已收錄至https://github.com/likekk/-Blog歡迎你們star😘😘😘,共同進步。若是文章有出現錯誤的地方,歡迎你們指出。後期將在將GitHub上規劃前端學習的路線和資源分享。前端

前言

距離寫上一篇文章已通過去兩個月了(上一篇文章是2020年07月17日寫的),託更有點嚴重,一方面是這幾個月項目在趕,另外一方面是本身近兩個月以來變懶了(爲本身不想更新文章找藉口)😅😅😅,國慶假期,公司放八天假。git

本身沒有去回家,主要是回家有點遠,歷來這邊讀書到如今的國慶期間一直都沒有回過家,這七八天原本也打算出去玩的(確定須要安排時間出去玩),可是想一想仍是學點東西比較好。因而有了這篇博客。github

寫預編譯(執行期上下文)這篇博客的初衷是由於一方面方便本身複習,另外一方面是爲了後續出閉包、做用域和做用域鏈、this指向等等的博客作一些前期的準備,以上幾個知識點能夠說是在基礎中比較難以掌握的,也有可能會在一些面試中常常會問到的。web

正文

首先在講解預編譯以前,咱們先說下JavaScript的語言特色吧!說兩點比較重要的(比較優秀的兩點)面試

咱們知道JavaScript語言首先是單線程的,其次是解釋性語言。對於這兩點我相信你們都不陌生,接着咱們拓展開來,對於解釋執行只是發生在執行的最後一步,解釋執行以前還有兩步,簡單介紹一下JavaScript運行三部曲瀏覽器

  • 語法分析
  • 預編譯(全局預編譯階段【建立GO對象】和函數預編譯階段【AO】)
  • 解釋執行

語法分析:閉包

對於語法分析的話,大概過一下就能夠了,好比:是否少寫個括號,是否有中文等等一系列的問題。JavaScript解釋器會全篇掃描一下,檢查是否有錯誤,可是不會執行。編輯器

預編譯:函數

這個就比較有意思了,也是本篇博客的重點,預編譯的話主要分兩種吧!一種是函數體裏面的預編譯(AO),另外一種是全局環境的預編譯(GO),不懂?沒有關係,我會慢慢講到。學習

解釋執行:

對於解釋執行執行的話,我想我也能夠不用多說吧!就是在執行的時候解釋一行執行一行唄!

對於預編譯這個概念其實咱們有遇到過,只是咱們不知道它的專業名詞叫作預編譯

納尼?不信我,好吧!咱們看下是否遇到過,先簡單舉個例子來證實一下個人觀點

test();
function test(){
    console.log('a');
}
console.log(a);
var a=123;

提問:輸出什麼?

答案: a 和 undefined

相信你們均可以作出來,其實這裏面就已經包含預編譯的階段,最開始講的時候就說了,預編譯發生在函數執行的前一刻,因此在函數調用的時候就已經有預編譯這一個階段了。

好的,咱們再來看下另一個示例

test();
function test(){
 console.log('a');
}
test();
console.log(a);
var a=123;

答案: a 、 a 和 undefined

對於如此簡單的兩道題目,相信在座的各位沒有作不出來的吧!

路人甲:「楊戩哥,這兩道題目好簡單(Low)呀!」

楊戩:」是,是很簡單,可是大家知道是什麼原理嗎?爲何第一題中輸出a和undefined,對於a的話相信你們都知道,可是undefined輸出是爲何?你們是否有考慮過。「

路人乙:」楊戩哥,咱們老師講過兩句比較有用的話「

函數聲明,總體提高

變量 聲明提高

路人丙:」這兩句話能夠解決不少問題,我在碰到一些問題的時候,就是套用這兩句話。「

楊戩:」路人丙弟弟,你的這兩句話確實能夠解決不少問題,可是有些問題靠這兩句話是解決不了的「

路人丙:」楊戩哥,我不信「

楊戩:」好吧!,既然你不信,那我就出道題考考你。「

路人丙:」come on「

function foo(a){
 console.log(a);
 var a=123;
 console.log(a);
 function a(){}
 console.log(a);
 var b=function(){}
 console.log(b);
 function d(){}
}
foo(1);

楊戩:」提問console.log()都輸出是什麼?「

路人丙:」這、這、這,還有這操做「

路人丙:」算了,我仍是老老實實聽楊戩講吧!,不裝逼了「

我先公佈如下答案吧!

答案:function a(){}、12三、12三、function(){}

可是到這裏我仍是沒有那麼快講解預編譯,考慮了一下,講解以前仍是須要鋪墊一點東西,不然很難講清楚。

預編譯前期

預編譯前期主要講解兩個東西

一、imply global 暗示全局變量,即任何變量,若是變量未經聲明就賦值,此變量就爲全局對象全部

二、一切聲明的全局變量都是window屬性

例一

var a=123;
console.log(a);
function test(){
    var a=b=123;
}
test();
console.log(window.a);
console.log(window.b);

依次輸出undefined、123

看第一條,即任何變量,若是變量未經聲明就賦值,此變量就爲全局對象全部

a變量咱們是已經聲明瞭,可是b變量咱們並無聲明,此時就進行賦值,因此歸全局對象全部

例二

  var b=123;
 console.log(b);
    console.log(window.b);

此時b===window.b

第二條,一切聲明的全局變量都是window屬性

講解的不是那麼透徹,等講徹底局預編譯的時候再回過頭來看,你就會有一種醍醐灌頂的感受

預編譯(執行期上下文)

預編譯在這裏我主要分兩種,一種是函數預編譯(函數執行期上下文)和全局預編譯(全局執行期上下文)

函數預編譯(函數執行期上下文)

函數預編譯我給你們總結了四條規律,不管什麼樣的,都可以正確的避坑

  • 建立AO對象
  • 找形參和變量聲明,將變量的形參名做爲AO的屬性名,值爲undefined
  • 將實參值和形參進行統一
  • 在函數體裏面找函數聲明,值賦予函數體

根據這四條法則咱們回到最開始的題目進行講解

例一

    function foo(a){
        console.log(a);
        var a=123;
        console.log(a);
        function a(){}
        console.log(a);
        var b=function(){}
        console.log(b);
        function d(){}
    }
    foo(1);

    /***
     * 1.建立AO對象
     * AO{
     *
     * }
     * 2.找形參和變量聲明,將變量的形參名做爲AO對象的屬性名,值爲undefined
     * AO{
     *     a:undefined
     *     b:undefined
     * }
     * 注:因爲形參a和變量聲明a相同,取其中一個便可
     *
     * 3.將實參值和形參進行統一
     * AO{
     *     a:undefined=>a:1,
     *     b:undefined
     * }
     *
     * 4.在函數體裏面找函數聲明,值賦予函數體
     *
     * AO{
     *     a:1=>function a(){},
     *     b:undefined,
     *     d:function d(){}
     * }
     * 此時預編譯結束,開始函數執行
     */


根據步驟分析

一、建立AO對象

二、找形參和變量聲明,將變量的形參名做爲AO的屬性名,值爲undefined

形參:a

變量聲明:a,b

因爲形參和變量聲明都是同一個,因此確定出現覆蓋的狀況,取其中一個就能夠了,此時AO對象中含有兩個屬性

AO{
 a:undefined,
 b:undefined,
}

三、將實參值和形參進行統一

實參值:1

AO{
 a:1,
 b:undefined
}

此時的a從undefined變成1

四、在函數體裏面找函數聲明,值賦予函數體

函數聲明:function a(){},function d(){}

注意:b是函數表達式,不要弄錯了

AO{
 a:function a(){},
    b:undefined,
    d:function d(){}    
    
}

此時預編譯結束,函數開始執行,解釋一行執行一行

第一個輸出function a(){},到了第二個的時候,var a已經提高了,可是a=123沒有調用,因此第二個輸出123

第三個的時候,function a(){}已經進行提高了,因此這一行代碼不要看,直接輸出123,同理b=function (){}賦值也同樣。

答案依次是:function a(){},123,123,function (){}

例二

    function test(a,b{
        console.log(a);
        c=0;
        var c;
        a=3;
        b=2;
        console.log(b);
        function b({}
        function d({}
        console.log(b);
    }
    test(1);
    /***
     * 1.建立AO對象
     * AO{
     *
     * }
     * 2.找形參和變量聲明,將變量的形參名做爲AO的屬性名,值爲undefined
     *  AO{
     *     a:undefined,
     *     b:undefined,
     *     c:undefined,
     *  }
     * 3.將實參值和形參進行統一
     *  AO{
     *      a:undefined=>a:1,
     *      b:undefined,
     *      c:undefined
     *  }
     *  4.在函數體裏面找函數聲明,值賦予函數體
     *  AO{
     *      a:1,
     *      b:undefined=>function b(){},
     *      c:undefined,
     *      d:function d(){}
     *  }
     *  預編譯階段結束,函數開始執行
     *
     */


一、建立AO對象

二、找形參和變量聲明,將變量的形參名做爲AO對象的屬性名,值爲undefined

形參:a,b

變量聲明:c

AO{
 a:undefined,
    b:undefined,
    c:undefined,
}

三、將實參值和形參進行統一

實參值:1

AO{
    a:1,
    b:undefined,
    c:undefined,
}

四、在函數體裏面找函數聲明,值賦予函數體

函數聲明:function b(){},function d(){}

AO{
    a:1,
    b:function b(){},
    c:undefined,
    d:functiond(){}
}

預編譯階段結束,函數開始執行

答案:1,2,2

注意:函數執行上下文發生在函數執行的前一刻

全局預編譯(全局執行期上下文)

to be honest,全局預編譯和函數預編譯都差很少,少了中間的第三步,將實參值和形參進行統一

  • 建立GO對象
  • 找變量聲明,值爲undefined
  • 找函數聲明,值賦予函數體

函數預編譯發生在函數執行的前一刻,而全局預編譯發生在script標籤建立的時候,能夠看做script是一個大的function。

全局執行上下文和函數執行上下文同時使用纔是真香,一塊兒來看以下兩個示例

例一

    console.log(test);
    function test(test{
        console.log(test);
        var test=234;
        console.log(test)
        function test({}
    }
    test(1);
    var test=123;

    /***
     * 1.建立GO對象
     * GO{
     *
     * }
     * 2.找變量聲明,值爲undefined
     * GO{
     *     test:undefined
     * }
     * 3.找函數聲明,值賦予函數體
     * GO{
     *     test:undefined=>function test(){....此處省略},
     * }
     * 全局預編譯結束,開始執行代碼
     *
     */


    /***
     * 1.建立AO對象
     * AO{
     *
     * }
     * 2.找形參和變量聲明,將形參的變量名做爲AO對象的屬性名,值爲undefined,
     *  AO{
     *     test:undefined,
     *  }
     *  3.將實參值和形參值統一
     *  AO{
     *      test:undefined=>test:1,
     *  }
     *  4.在函數體裏面找函數聲明,值賦予函數體
     *  AO{
     *      test:1=>test:function test(){}
     *  }
     *  函數預編譯結束,開始執行代碼
     *
     *
     *
     */


1、全局預編譯

一、建立GO對象

GO{

}

二、找變量聲明,值爲undefined

變量聲明:test

GO{
 test:undefined
}

三、找函數聲明,值賦予函數體

函數聲明:function test(test){....}

GO{
    test:function test(test){....}
}

全局預編譯結束,開始執行代碼,進入函數預編譯

2、函數預編譯

一、建立AO對象

AO{
    
}

二、找形參和變量聲明,將形參的變量名做爲AO的屬性名,值爲undefined

形參:test

變量聲明:test

AO{
    test:undefined
}

三、將實參值和形參統一

實參值:1

AO{
    test:1
}

四、在函數體裏找函數聲明,值賦予函數體

函數聲明:function test(){}

AO{
    test:function test(){}
}

函數預編譯結束,開始執行代碼

答案:function test(test){....},function test(){},234

這裏須要多分析全局預編譯,仍是萬變不離其中

例二

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

    /***
     * 1.建立GO對象
     * GO{
     *
     * }
     * 2.找變量聲明,值爲undefined
     * GO{
     *     global:undefined
     * }
     * 3.找函數聲明,值賦予函數體
     * GO{
     *     global:undefined,
     *     fn:function fn(){...}
     * }
     *
     * 全局預編譯結束,執行代碼,進入函數預編譯
     */


    /***
     *1.建立AO對象
     * AO{
     *
     * }
     * 2.找形參和變量聲明,將形參的變量名做爲AO的屬性名,值爲undefined
     *  AO{
     *      global:undefined
     *  }
     * 3.將實參值和形參統一
     *  AO{
     *      global:undefined
     *  }
     * 4.在函數體裏面找函數聲明,值賦予函數體
     * AO{
     *     global:undefined
     * }
     *函數預編譯結束,開始執行代碼
     *
     */

1、全局預編譯

一、建立GO對象

GO{
    
}

二、找變量聲明,值爲undefined

變量聲明:global

GO{
    global:undefined
}

三、找函數聲明,值賦予函數體

GO{
    global:undefined,
    fn:function fn(){.....}
}

全局預編譯完成,執行代碼,進入函數預編譯

2、函數預編譯

一、建立AO對象

AO{
    
}

二、找形參和變量聲明,將形參的變量名做爲AO對象的屬性名,值爲undefined

形參:沒有

變量聲明:global

AO{
    global:undefined
}

三、將實參值和形參統一

實參值:沒有

AO{
    global:undefined
}

四、在函數體裏找函數聲明,值賦予函數體

函數聲明:沒有

AO{
    global:undefined
}

函數預編譯完成,執行代碼

答案:undefined,200

在這裏涉及一點點做用域和做用域鏈的知識,就近原則,本身有就用本身的,本身沒有就看下GO裏面是否有,若是都沒有就是undefined,

函數執行的時候,本身裏面有global,因此就用本身的。

如今回過頭來看如今這兩句話

  • 一、imply global 暗示全局變量,即任何變量,若是變量未經聲明就賦值,此變量就爲全局對象全部

  • 二、一切聲明的全局變量都是window屬性

能夠確認的是GO就是window,從window裏面取值就是從GO裏面取值。

經典題目

筆者找了一道很是有意思的題目,本身按照步驟也作錯了,因此在這裏分享一下給你們

    a=100;
    function demo(e{
        function e({}
        arguments[0]=2;
        console.log(e);
        if(a){
            var b=123;
            function c({}
        }
        var c;
        a=10;
        var a;
        console.log(b);
        f=123;
        console.log(c);
        console.log(a);
    }
    var a;
    demo(1);
    console.log(a);
    console.log(f);
    /***
     * 1.建立GO對象
     * GO{
     *
     * }
     * 2.着變量聲明,值爲undefined
     * GO{
     *     a:undefined
     * }
     * 3.找函數聲明,值賦予函數體
     * GO{
     *     a:undefined,
     *     demo:function demo(e){...}
     * }
     * 全局預編譯完成,執行代碼進入函數預編譯
     *
     */

    /***
     *1.建立AO對象
     * AO{
     *
     * }
     * 2.找形參和變量聲明,將形參的變量名做爲AO對象的屬性名,值爲undefined
     *  AO{
     *      e:undefined,
     *      a:undefined,
     *      b:undefined,
     *      c:undefined,
     *
     *  }
     *  3.將實參值和新參統一
     *  AO{
     *      e:1,
     *      a:undefined,
     *      b:undefined,
     *      c:undefined,
     *  }
     *  4.在函數體裏面找函數聲明,值賦予函數體
     *  AO{
     *      e:function e(){},
     *      a:undefined,
     *      b:undefined,
     *      c:function c(){}
     *  }
     *  函數預編譯完成,執行代碼
     */

1、全局預編譯

一、建立GO對象

GO{

}

二、找變量聲明,值爲undefined

變量聲明:a

GO{
    a:undefined
}

三、找函數聲明,值賦予函數體

函數聲明:function demo(e){...}

GO{
    a:undefined,
    demo:function demo(e){...}
}

全局預編譯完成,執行代碼,進入函數預編譯

2、函數預編譯

一、建立AO對象

AO{
    
}

二、找形參和變量聲明,將形參的變量名做爲AO對象的屬性名,值爲undefined

形參:e

變量聲明:a,b,c

AO{
    e:undefined,
    a:undefined,
    b:undefined,
    c:undefined
}

三、將實參值和形參統一

實參值:1

形參:e

AO{
    e:1,
    a:undefined,
    b:undefined,
    c:undefined
}

四、在函數體裏面找函數聲明,值賦予函數體

函數聲明:function e(){},function c(){}

AO{
    e:function e(){},
    a:undefined
    b:undefined
    c:function c(){}
}

函數預編譯完成,執行代碼

理想答案

2,undefined,function c(){},10,100,123

實際答案

2,undefined,undefined, 10,100 ,123

因爲谷歌瀏覽器的新規定,不能在if語句裏面定義函數,因此,function c(){}沒法提高

致謝讀者

看到這裏的讀者都是最帥的,最美的,本篇是我嘗試寫文章的新作法,第一次搞這種類型的文章,由於但願本身的文章閱讀起來能夠沒有那麼枯燥(能夠從中獲取更多的快樂),學習是一件特別痛苦的事情,看文章也是,我也但願本身的文章能夠更加的生動、幽默。讓看文章的你既能夠學到東西也不會那麼枯燥。

若是您有更好的建議,請在下方留下您寶貴的評論。

結尾

若是以爲本篇文章對您有用的話,能夠麻煩您幫忙點亮那個點贊按鈕嗎?

對於二郎神楊戩這個暖男來講:真的真的很是有用,您的支持將是我繼續寫文章前進的動力,咱們下篇文章見。

相關文章
相關標籤/搜索