進擊的 JavaScript(三) 之 函數執行過程

理解js 的執行過程是很重要的,比如,作用域,作用域鏈,變量提升,閉包啊,要想明白這些,你就得搞懂函數執行時到底發生了什么!

一、執行環境(Execution Context)又稱執行上下文

當代碼執行時都會產生一個執行環境。JavaScript中的執行環境可以分為三種。

  1. 全局環境:在瀏覽器中,全局環境被認為是window對象,因此,所有的全局變量和函數都作為window對象的 屬性 和 方法 創建的。
  2. 函數環境:當一個函數執行時,就會創建該函數的執行環境,在其中執行代碼。
  3. eval(不建議使用,可忽略)

函數內,沒有使用var 聲明的變量,在非嚴格模式下為window的屬性,即全局變量。

二、函數調用棧(call stack)

js 是根據函數的調用(執行) 來決定 執行順序的。每當一個函數被調用時,js 會為其創建執行環境,js引擎就會把這個執行環境 放入一個棧中 來處理。

這個棧,我們稱之為函數調用棧(call stack)。棧底永遠都是全局環境,而棧頂就是當前正在執行函數的環境。當棧頂的執行環境 執行完之后,就會出棧,并把執行權交給之前的執行環境。

看栗子說話:

function A(){
   console.log("this is A");
   function B(){
       console.log("this is B");
   }
   B();
}

A();

那么這段代碼執行的情況就是這樣了。

  1. 首先 A() ;A 函數執行了,A執行環境入棧。
  2. A 函數執行時,遇到了 B(),B 又執行了,B入棧。
  3. B 中沒有可執行的函數了,B執行完 出棧。
  4. 繼續執行A, A中沒有可執行的函數了,A執行完 出棧。
函數調用棧

再來個不常規的:

function A(){
    
    function B(){
        console.log(666);
    }
 
    return B;
}

var C = A();
C();
//666
  1. 首先 A() ;A 函數執行了,A執行環境入棧。
  2. 繼續執行A, A中沒有可執行的函數了,A執行完 出棧。
  3. 然后C(), 這時的C 就是 B,A 執行后,把B返回 賦值給了C,B執行環境入棧。
  4. B 中 沒有可執行的函數了,B執行完 出棧。
函數調用棧

眼尖的同學,估計看出來了,它怎么像閉包呢?其實,稍微改動下,它就是閉包了。

function A(){
    
    var say = 666
    
    function B(){
        console.log(say);
    }
 
    return B;
}

var C = A();

C();

//666

這就是閉包了,但是這次我們不講閉包,你就知道,它是的執行是怎么回事就行。

三、執行過程

現在我們已經知道,每當一個函數執行時,一個新的執行環境就會被創建出來。其實,在js引擎內部,這個環境的創建過程可分為兩個階段:

A. 建立階段(發生在調用(執行)一個函數時,但是在執行函數內部的具體代碼之前)
? ? ? ?1.建立活動對象;
? ? ? ?2.構建作用域鏈;
? ? ? ?3.確定this的值。

B. 代碼執行階段(執行函數內部的具體代碼)
? ? ? ?1.變量賦值;
? ? ? ?2.執行其它代碼。

需要注意的是,作用域鏈是創建函數的時候就創建了,此時的鏈只有全局變量對象,保存在函數的[[Scope]]屬性中,然后函數執行時的,只是通過復制該屬性中的對象 來 構建作用域鏈。本文后面還有說明。

看圖更清晰!

執行上下文

如果把函數執行環境看成一個對象的話:

executionContextObj = {           //執行上下文對象
            AtiveObject: { },  //活動對象
            scopeChain: { },      //作用域鏈
            this: {}              //this
}

//下面這段內容,感興趣的可以看下,不感興趣,就跳過哈。
也許你在別家看到跟我的不一樣,人家寫的是建立變量對象。下面我來說說我得想法吧!

之前我按照 首先建立變量對象,其后,變量對象轉變為活動對象的規則 去理解,但是呢,通過我分析JavaScript高級程序設計第三版,4.2節 和 7.2節,發現根本就不符合邏輯。

然后,我根據分析,得出了我的結論:變量對象 是執行環境中保存著環境中定義的所有變量和函數 的對象 的統稱。而活動對象,是函數執行環境中創建的,它不僅保存著函數執行環境中定義的變量和函數,并且獨有一個arguments 屬性。因此,活動對象也可稱之為變量對象。

這樣,很多東西就說的通了。
比如(以下都是來自JavaScript高級程序設計第三版,4.2節 和 7.2節 中原文):

如果這個環境是函數,則將其活動對象(activation object)作為變量對象。活動對象在最開始時只包含一個變量,即 arguments 對象(這個對象在全局環境中是不存在的)。

當某個函數被調用時,會創建一個執行環境(execution context)及相應的作用域鏈。
然后,使用 arguments 和其他命名參數的值來初始化函數的活動對象。

每個執行環境都有一個表示變量的對象——變量對象。

此后,又有一個活動對象(在此作為變量對象使用)被創建并被推入執
行環境作用域鏈的前端。對于這個例子中 compare() 函數的執行環境而言,其作用域鏈中包含兩個變量對象:本地活動對象和全局變量對象。

有興趣的可以去看看這本書上說的,有不同的想法可以積極留言,咱們好好探討,哈哈。

(一)建立階段

1、建立活動對象(AO)

A. 建立arguments對象,檢查當前上下文中的參數,建立該對象下的屬性以及屬性值 。

B. 檢查當前環境中的函數聲明(使用function 聲明的)。每找到一個函數聲明,就在活動對象下面用函數名建立一個屬性,屬性值就是指向該函數在內存中的地址的一個引用,如果上述函數名已經存在于活動對象下,那么則會被新的函數引用所覆蓋。

C. 檢查當前上下文中的變量聲明(使用 var 聲明的)。每找到一個變量聲明,就在活動對象下面用變量名建立一個屬性,該屬性值為undefined。如果該屬性名已存在,則忽略新的聲明。

function test(){
    function a(){};
    var b;
}
test();

test 函數 的活動對象:

testAO: {    //test變量對象
    arguments: { ... };
    a:function(){};
    b:undefined
}  

變量作用域
javaScript 中,只有兩種變量作用域,一種是局部變量作用域,又稱函數作用域。另一個則是全局作用域。

什么變量提升問題的根本原因就在建立階段了。

console.log(A);

function A(){};

console.log(B);

var A = 666;
var B = 566;

console.log(A);
console.log(B);

//function A
//undefined
//666
//566

上面的實際順序就是這樣的了

function A(){};
//var A; 這個var 聲明的 同名 A,會被忽略

var B = undefined;

console.log(A);

console.log(B);

A = 666;   //給A 重新賦值
B = 566;   //給B 賦值

console.log(A);
console.log(B);

注意第三點,使用var 聲明時,如果VO對象下,該屬性已存在,忽略新的var 聲明。
因為A 使用 function 聲明,VO對象下,創建A屬性,然后 var 聲明時,檢索發現已經有該屬性了,就會忽略 var A 的聲明,不會把A 設置為 undefined。

2、構建作用域鏈
作用域鏈的最前端,始終都是當前執行的代碼所在函數的活動對象。下一個AO(活動對象)為包含本函數的外部函數的AO,以此類推。最末端,為全局環境的變量對象。

注意:雖然作用域鏈是在函數調用時構建的,但是,它跟調用順序(進入調用棧的順序)無關,因為它只跟 包含關系(函數 包含 函數 的嵌套關系) 有關。

可能比較繞口,還是來個小栗子,再來個圖

function fa(){
    var va = "this is fa";
    
    function fb(){
        var vb = "this is fb";
    
        console.log(vb);
        
        console.log(va);
    }
    return fb;
}
var fc = fa();
fc();

//"this is fb"
//"this is fa"

函數調用棧的情況就是這樣:

函數調用棧

那么把函數 fb 的執行環境比作對象(建立階段):

fbEC = {           //執行上下文對象

            fbAO: {   //活動對象 AO
            
                  arguments: { ... };   //arguments 對象

                  vb: undefined   //變量聲明建立的屬性,設置為undefined
            },
            
            scopeChain: [ AO(fa), AO(fb), VO(window) ],      //作用域鏈
            
            this: { ... }              //this
}

fb作用域的展開就是這樣的:

作用域鏈

fb 函數 被 fa 函數 包含, fa 函數 被 window 全局環境包含。作用域鏈只跟包含關系有關!

注意:作用域鏈是單向的,因此,函數內的可以訪問函數外 和 全局的變量,函數,但是反過來,函數外,全局內 不能訪問函數內的變量,函數。

3、確定 this 指向
所以說 this 的指向,是在函數執行時確定的。

(二)執行階段

1、變量賦值
根據代碼順序執行,遇到變量賦值時, 給對應的變量賦值。

function getColor(){
    console.log(color);
    
    var color;
    console.log(color);
    
    color = "red";
    console.log(color);
}
getColor();
//undefined
//undefined
//"red";

3、執行其他代碼。

當函數執行完畢后,局部活動對象就會被銷毀(也就是說,局部的變量,函數,arguments 等都會被銷毀),內存中僅保存全局作用域(全局執行環境的變量對象)。

這句話對理解閉包很重要,隨后,我會出一個閉包的文章,敬請期待!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容