❝點贊再看,年薪百萬 本文已收錄至https://github.com/likekk/-Blog歡迎大家star???,共同進步。如果文章有出現錯誤的地方,歡迎大家指出。後期將在將GitHub上規劃前端學習的路線和資源分享。
❞
前言
距離寫上一篇文章已經過去兩個月了(上一篇文章是2020年07月17日寫的),託更有點嚴重,一方面是這幾個月專案在趕,另一方面是自己近兩個月以來變懶了(為自己不想更新文章找藉口)???,國慶假期,公司放八天假。
自己沒有去回家,主要是回家有點遠,從來這邊讀書到現在的國慶期間一直都沒有回過家,這七八天本來也打算出去玩的(肯定需要安排時間出去玩),但是想想還是學點東西比較好。於是有了這篇部落格。
寫預編譯(執行期上下文)這篇部落格的初衷是因為一方面方便自己複習,另一方面是為了後續出閉包、作用域和作用域鏈、this指向等等的部落格做一些前期的準備,以上幾個知識點可以說是在基礎中比較難以掌握的,也有可能會在一些面試中經常會問到的。
正文
首先在講解預編譯之前,我們先說下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(){}、123、123、function(){}
❞
但是到這裡我還是沒有那麼快講解預編譯,考慮了一下,講解之前還是需要鋪墊一點東西,否則很難講清楚。
預編譯前期
預編譯前期主要講解兩個東西
❝1、imply global 暗示全域性變數,即任何變數,如果變數未經宣告就賦值,此變數就為全域性物件所有
2、一切宣告的全域性變數都是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(){}
* }
* 此時預編譯結束,開始函式執行
*/
根據步驟分析
1、建立AO物件
2、找形參和變數宣告,將變數的形參名作為AO的屬性名,值為undefined
形參:a
變數宣告:a,b
由於形參和變數宣告都是同一個,所以肯定出現覆蓋的情況,取其中一個就可以了,此時AO物件中含有兩個屬性
AO{
a:undefined,
b:undefined,
}
3、將實參值和形參進行統一
實參值:1
AO{
a:1,
b:undefined
}
此時的a從undefined變成1
4、在函式體裡面找函式宣告,值賦予函式體
函式宣告: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(){}
* }
* 預編譯階段結束,函式開始執行
*
*/
1、建立AO物件
2、找形參和變數宣告,將變數的形參名作為AO物件的屬性名,值為undefined
形參:a,b
變數宣告:c
AO{
a:undefined,
b:undefined,
c:undefined,
}
3、將實參值和形參進行統一
實參值:1
AO{
a:1,
b:undefined,
c:undefined,
}
4、在函式體裡面找函式宣告,值賦予函式體
函式宣告: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{
}
2、找變數宣告,值為undefined
變數宣告:test
GO{
test:undefined
}
3、找函式宣告,值賦予函式體
函式宣告:function test(test){....}
GO{
test:function test(test){....}
}
全域性預編譯結束,開始執行程式碼,進入函式預編譯
二、函式預編譯
1、建立AO物件
AO{
}
2、找形參和變數宣告,將形參的變數名作為AO的屬性名,值為undefined
形參:test
變數宣告:test
AO{
test:undefined
}
3、將實參值和形參統一
實參值:1
AO{
test:1
}
4、在函式體裡找函式宣告,值賦予函式體
函式宣告: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{
}
2、找變數宣告,值為undefined
變數宣告:global
GO{
global:undefined
}
3、找函式宣告,值賦予函式體
GO{
global:undefined,
fn:function fn(){.....}
}
全域性預編譯完成,執行程式碼,進入函式預編譯
二、函式預編譯
1、建立AO物件
AO{
}
2、找形參和變數宣告,將形參的變數名作為AO物件的屬性名,值為undefined
形參:沒有
變數宣告:global
AO{
global:undefined
}
3、將實參值和形參統一
實參值:沒有
AO{
global:undefined
}
4、在函式體裡找函式宣告,值賦予函式體
函式宣告:沒有
AO{
global:undefined
}
函式預編譯完成,執行程式碼
❝答案:undefined,200
❞
在這裡涉及一點點作用域和作用域鏈的知識,就近原則,自己有就用自己的,自己沒有就看下GO裡面是否有,如果都沒有就是undefined,
函式執行的時候,自己裡面有global,所以就用自己的。
現在回過頭來看現在這兩句話
1、imply global 暗示全域性變數,即任何變數,如果變數未經宣告就賦值,此變數就為全域性物件所有
2、一切宣告的全域性變數都是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{
}
2、找變數宣告,值為undefined
變數宣告:a
GO{
a:undefined
}
3、找函式宣告,值賦予函式體
函式宣告:function demo(e){...}
GO{
a:undefined,
demo:function demo(e){...}
}
全域性預編譯完成,執行程式碼,進入函式預編譯
二、函式預編譯
1、建立AO物件
AO{
}
2、找形參和變數宣告,將形參的變數名作為AO物件的屬性名,值為undefined
形參:e
變數宣告:a,b,c
AO{
e:undefined,
a:undefined,
b:undefined,
c:undefined
}
3、將實參值和形參統一
實參值:1
形參:e
AO{
e:1,
a:undefined,
b:undefined,
c:undefined
}
4、在函式體裡面找函式宣告,值賦予函式體
函式宣告: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(){}無法提升
致謝讀者
看到這裡的讀者都是最帥的,最美的,本篇是我嘗試寫文章的新做法,第一次搞這種型別的文章,因為希望自己的文章閱讀起來可以沒有那麼枯燥(可以從中獲取更多的快樂),學習是一件特別痛苦的事情,看文章也是,我也希望自己的文章可以更加的生動、幽默。讓看文章的你既可以學到東西也不會那麼枯燥。
如果您有更好的建議,請在下方留下您寶貴的評論。
結尾
如果覺得本篇文章對您有用的話,可以麻煩您幫忙點亮那個點贊按鈕嗎?
對於二郎神楊戩這個暖男來說:「真的真的非常有用」,您的支援將是我繼續寫文章前進的動力,我們下篇文章見。