原來JS函數(shù)提升 變量提升原來是這樣

1.一些常見術(shù)語

  • AO:函數(shù)執(zhí)行前產(chǎn)生的一個對象
  • GO:全局對象
  • VO:變量對象库北,一般指向AO或GO
  • ECS:上下文執(zhí)行棧
  • GECS:全局上下文執(zhí)行棧
  • FECS:函數(shù)上下文執(zhí)行棧

2.全局代碼的執(zhí)行過程

這是一段全局代碼

var name="why";
console.log(num1);
var num1=20;
var num2=30;
var result=num1+num2
console.log(result);
  1. 在代碼編譯之前渔隶,會創(chuàng)建一個GlobalObject對象鞍爱,這個對象包括一些全局的其他對象历等,并創(chuàng)建一個window屬性指向這個對象本身。

  2. 在全局代碼編譯的時候挣棕,遇到普通變量會放入到GlobalObject這個對象中宝泵,賦值為undefined

  3. 全局代碼執(zhí)行時丛肮,會先創(chuàng)建一個Global Execution Stack 全局執(zhí)行棧墙牌,里面包括兩個部分

    • Variable Object:在全局執(zhí)行棧中涡驮,這個對象是指向的GlobalObject
    • 執(zhí)行代碼
  4. 執(zhí)行的代碼,遇到變量要取值時喜滨,會先從VO找對應(yīng)的值捉捅,從上往下依次執(zhí)行

第二行輸出時,要取num1的值虽风,去VO找的時候棒口,因為還沒執(zhí)行到后面,所以此時的值是undefined

6.png

3. 全局代碼的執(zhí)行過程(函數(shù))

函數(shù)和普通變量是不一樣辜膝,如果在聲明之前提前調(diào)用无牵,還是會正常執(zhí)行結(jié)果,而普通變量則是undefined

var name="why";
foo()
function foo(){
  console.log(m);
  var m=10;
  var n=20;
  console.log("foo");
}
  1. 在編譯代碼之前内舟,會創(chuàng)建一個全局GO對象合敦,放入相關(guān)屬性

  2. 在編譯代碼的時候,將name放入到GO對象中验游,設(shè)置其值為undefined。然后將foo放入到GO中保檐,發(fā)現(xiàn)foo是一個函數(shù)耕蝉,所以會在內(nèi)存中開辟一片空間,來保存這個函數(shù)夜只,這個內(nèi)存空間包含兩個部分垒在,一個是父級作用域,一個是函數(shù)執(zhí)行體扔亥。然后將GO.foo的值設(shè)置為這片空間的內(nèi)存地址场躯。

    var GO={
        name:undefined,
        foo:'0x001'
    }
    
  3. 在執(zhí)行全局代碼的時候,會創(chuàng)建一個GES全局執(zhí)行棧旅挤,然后將GES放入到ECS上下文執(zhí)行棧中

    • GES包括兩個部分
      • VO(Variable Object):此時它指向的是GO
      • 執(zhí)行代碼
  4. 執(zhí)行全局代碼踢关,執(zhí)行第一行時,將VO.name的值設(shè)置 為"why"

  5. 執(zhí)行第二行時粘茄,去取foo的值签舞,然后從VO中找秕脓,返回一個內(nèi)存地址。但是發(fā)現(xiàn)這個foo函數(shù)會執(zhí)行儒搭。函數(shù)執(zhí)行的時候會自動創(chuàng)建一個函數(shù)上下文執(zhí)行棧FES(Functional Execution Stack),FES包括兩個部分

    • VO(Variable Object):AO(Activation Object)

      • AO:在函數(shù)編譯前會創(chuàng)建一個AO對象吠架,它在編譯的時候,會將m搂鲫、n放入到這個對象內(nèi)部傍药,然后設(shè)置其值為undefined

        var AO={
            m:undefined,
            n:undefined
        }
        
    • 執(zhí)行代碼

  6. 在執(zhí)行foo函數(shù)的時候,先輸出m,從VO中查找,輸出undefined魂仍,然后會將m怔檩、n變量設(shè)置為具體的值

    var AO={
        m:10,
        n:20
    }
    
  7. 執(zhí)行完foo函數(shù)后,這個函數(shù)上下文執(zhí)行棧會移出上下文棧蓄诽,然后銷毀薛训,如果AO對象沒有任何引用的話,后面也會被銷毀

4. 作用域鏈

var name="why";
foo()
function foo(){
  console.log(m);
  var m=10;
  var n=20;
  console.log(name);
}

當我們查找變量時仑氛,是沿著作用域鏈進行查找的乙埃。所以輸出的值是"why"

因為函數(shù)會存在嵌套,如果還是沒找到锯岖,會繼續(xù)往上一層進行查找介袜,一層一層往上找,直到全局作用域中出吹,如果還是沒有找到遇伞,則會報錯。

上面在VO查找的時候捶牢,發(fā)現(xiàn)沒有name鸠珠,然后再往父級作用域中查找GO,發(fā)現(xiàn)有name秋麸,則返回對應(yīng)name對應(yīng)的值

其實函數(shù)執(zhí)行棧包含的兩部分

  • 第一部分不僅僅包含 VO渐排,還包含作用域鏈:這個作用域鏈是由當前的VO和ParentScope
    • 父級作用域其實在編譯的時候就已經(jīng)確定好的,所以foo父級作用域是GO
    • 所以作用域鏈是VO+GO

5. 全局代碼執(zhí)行的過程(函數(shù)嵌套)

var name="why";
foo()
function foo(){
  console.log(m);
  var m=10;
  var n=20;
  function bar(){
    console.log(name);
  }
  bar()
}

如果一個函數(shù)嵌套另外一個函數(shù)灸蟆,另外一個函數(shù)剛開始是不需要被執(zhí)行的時候驯耻,是不會被編譯的,只會被預(yù)編譯炒考,

  • 例如bar函數(shù)執(zhí)行時可缚,也會自動創(chuàng)建函數(shù)上下文執(zhí)行棧FES,里面包含兩個部分:

    • 第一部分
      • VO:AO
        • AO:arguments
      • scope-chain:VO+ParentSope
      • this:是在運行時進行綁定的
    • 第二部分
      • 執(zhí)行代碼
  • 執(zhí)行第8行代碼的時候,輸出name的值斋枢,會先從當前的VO中查找name帘靡,發(fā)現(xiàn)不存在,去父級作用域(foo)的VO中查找杏慰,發(fā)現(xiàn)還是不存在测柠,就是去父級作用域(foo)的父級作用域(GO)中去查找,發(fā)現(xiàn)有name的值炼鞠,是"why",所以將其輸出"why"轰胁。

8.png

6. 函數(shù)調(diào)用函數(shù)的執(zhí)行過程

var message="hello Global";
function foo(){
  console.log(message);
}
function bar(){
  var message="hello bar";
  foo();
}
bar();

打印的結(jié)果是 hello Global

  • 函數(shù)的作用域是在編譯的時候就已經(jīng)確定了
  • 函數(shù)的作用域跟它定義的位置有關(guān)系谒主,跟調(diào)用的位置是沒有關(guān)系的。
9.png

7. 變量環(huán)境(Variable Enviroment)和環(huán)境記錄(Environment Record)

其實上面的講解是基于早期ECMA的版本規(guī)范:

  • VO赃阀、GO霎肯、AO 這是ECMAScript5以前的規(guī)范
    • 每個執(zhí)行上下文(GEC、FEC)會被關(guān)聯(lián)到一個變量對象中(Variable Object)榛斯,在源代碼中聲明的變量和聲明的函數(shù)都會作為屬性放入到VO中观游。對于函數(shù)來說,參數(shù)也會放入到VO中驮俗。

在最新的ECMASCript規(guī)范中懂缕,對于一些詞匯作了一些修改:

  • 變量環(huán)境(VE)和環(huán)境記錄(ER)(不一定用對象實現(xiàn),也可以用map實現(xiàn))
    • 每個執(zhí)行上下文都會被關(guān)聯(lián)到一個變量環(huán)境中王凑,在執(zhí)行代碼中搪柑,聲明的變量和聲明的函數(shù)都會作為環(huán)境記錄添加到變量環(huán)境
    • 對于函數(shù)而言,參數(shù)也會作為環(huán)境記錄加入到變量環(huán)境中索烹。

通過上面的變化工碾,我們可以知道,VO變?yōu)樽兞凯h(huán)境(VE)

8.作用域提升面試題

8.1 面試題1

var n=100;
function foo(){
  n=200;
}
foo()
console.log(n);//200

8.2 面試題2

function foo(){
  console.log(n);//undefined
  var n=200;
  console.log(n); //200
}
var n=100;
foo()

輸出 undefined 200

8.3 面試題3


var n=100;
function foo1(){
  console.log(n);
}

function foo2(){
  var n=200;
  console.log(n);
  foo1()
}

foo2();
console.log(n);
  輸出 200 100 100                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               

8.4 面試題4

var a=100;
function foo(){
  console.log(a);
  return 
  var a=100;
}
foo()

輸出undefined

8.5 面試題5

function foo(){
  var m=200;
}
foo()
console.log(m);

會報錯百姓,報m找不到

  1. 執(zhí)行全局代碼前渊额,會先創(chuàng)建一個GO對象,對代碼進行編譯的時候垒拢,會將聲明的變量和聲明的函數(shù)作為屬性加入到GO中旬迹。

    var GO={
        foo:'0X001'
    }
    
  2. 在執(zhí)行全局代碼時,會創(chuàng)建一個全局執(zhí)行上下文棧VO指向GO子库,執(zhí)行第4行的時候舱权,執(zhí)行foo函數(shù)。執(zhí)行函數(shù)的時候會創(chuàng)建一個函數(shù)執(zhí)行上下文(FECS)仑嗅。FECS的VO執(zhí)行AO會先編譯foo函數(shù),將foo函數(shù)中聲明的變量和聲明的函數(shù)添加到FECS的AO對象中

    var AO={
        m:undefined
    }
    
  3. 執(zhí)行FECS中代碼時张症,將AO.m賦值為200.執(zhí)行完foo函數(shù)完后仓技,F(xiàn)ECS也移除上下文執(zhí)行棧,AO對象沒有被引用也隨之銷毀

  4. 執(zhí)行第5行時俗他,會去GO中查找m發(fā)現(xiàn)沒有脖捻,則會報一個錯誤 m is not defined

8.6 面試題6(特殊語法)

function foo(){
   m=200;
}
foo()
console.log(m);
  • 嚴格模式下,會報錯
  • 非嚴格模式下兆衅,輸出 200

如果在函數(shù)中沒有聲明 某個變量地沮,但是卻去賦值了嗜浮。它這個變量會先被定義到全局對象中,然后再去執(zhí)行賦值操作

8.7 面試題7

function foo(){
  var a=b=100;
}
foo()
console.log(a);
console.log(b);

輸出a的時候會報錯摩疑,因為a未在GO中聲明

輸出b的時候不會報錯危融,輸出的是100

10.png

9.總結(jié)

全局代碼執(zhí)行過程.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市雷袋,隨后出現(xiàn)的幾起案子吉殃,更是在濱河造成了極大的恐慌,老刑警劉巖楷怒,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛋勺,死亡現(xiàn)場離奇詭異,居然都是意外死亡鸠删,警方通過查閱死者的電腦和手機抱完,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刃泡,“玉大人巧娱,你說我怎么就攤上這事⊥苯” “怎么了家卖?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長庙楚。 經(jīng)常有香客問我上荡,道長,這世上最難降的妖魔是什么馒闷? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任酪捡,我火速辦了婚禮,結(jié)果婚禮上纳账,老公的妹妹穿的比我還像新娘逛薇。我一直安慰自己,他們只是感情好疏虫,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布永罚。 她就那樣靜靜地躺著,像睡著了一般卧秘。 火紅的嫁衣襯著肌膚如雪呢袱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天翅敌,我揣著相機與錄音羞福,去河邊找鬼。 笑死蚯涮,一個胖子當著我的面吹牛治专,可吹牛的內(nèi)容都是我干的卖陵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼张峰,長吁一口氣:“原來是場噩夢啊……” “哼泪蔫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挟炬,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鸥滨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后谤祖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婿滓,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年粥喜,在試婚紗的時候發(fā)現(xiàn)自己被綠了凸主。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡额湘,死狀恐怖卿吐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锋华,我是刑警寧澤嗡官,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站毯焕,受9級特大地震影響衍腥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纳猫,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一婆咸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芜辕,春花似錦尚骄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至状蜗,卻和暖如春乃沙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诗舰。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留训裆,地道東北人眶根。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓蜀铲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親属百。 傳聞我的和親對象是個殘疾皇子记劝,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容