從底層看JS執行機制

從一個簡單的例子出發

先從一個簡單的例子出發(先不涉及異步),看看本身是否大體瞭解瀏覽器的執行機制:html

console.log(a);
var a=1;
function foo(a){
    console.log(a);
    var a=2;
    console.log(a);
}
foo(a);
執行結果:

undefined 1 2git

若是你的預測結果不同,那你能夠看看下面幾個常見的誤區:github

  • var a=1不是進行了變量提高?爲何第一個打印的結果爲 undefined?
答:變量提高只提高變量的聲明,並不進行賦值。其中變量提高發生在預編譯階段,此時a=undefined,預編譯結束後代碼以下
//函數聲明和變量聲明進行提高,且函數聲明優先級更高
function foo(a){
    console.log(a);
    var a=2;
    console.log(a);
}
var a;
console.log(a);
a=1;
foo(a);

很明顯第一個結果爲undefined。web

  • foo函數中參數名和變量名都爲a,使用時該以哪個a爲準?
變量聲明在順序上跟在函數聲明和形式參數聲明以後,同時,若是變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性。例子中的var a=2,能夠拆分爲var a;a=2;其中a=2是對參數a進行賦值。

如今咱們詳細地說一說js的執行機制:

首先你須要理解以下幾個概念:數組

JavaScript中的堆和棧

  • 棧(stack) 棧stack爲自動分配的內存空間,它由系統自動釋放;
  • 堆(heap) 堆heap是動態分配的內存,大小不定也不會自動釋放。
JavaScript 中的變量分爲基本類型和引用類型。其中,基本類型存在於棧中,引用類型存在於堆中。在js的執行階段,當執行到a=2這樣的賦值語句時,js引擎線程會先判斷2是基本類型仍是引用類型,若是它是基本類型,則直接對執行棧中的AO進行賦值a=2(AO會在下面的執行上下文中講到),如果引用類型,則在堆中存入2,而後用2在堆中的地址對AO進行賦值。

執行環境

js的執行環境分爲三種:瀏覽器

  • 全局環境(JS代碼加載完畢後,進入代碼預編譯即進入全局環境)
  • 函數環境(函數調用執行時,進入該函數環境,不一樣的函數則函數環境不一樣)
  • eval(不建議使用,會有安全,性能等問題)

js每進入一個執行環境就會建立一個執行上下文,並將它放入執行棧中。執行上下文會在下文講到。安全

單線程(同步和異步)

js是一門單線程語言,但並不意味着參與js執行過程的線程就只有一個。一個有四個線程參與該過程:
JS引擎線程、事件觸發線程、定時器觸發線程、HTTP異步請求線程。其中,只有JS引擎線程在執行JS腳本程序,其餘三個線程只負責將知足觸發條件的處理函數推動事件隊列,等待JS引擎線程執行。異步

舉一個簡單的例子來講:函數

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

console.log('script end');
  1. JS引擎主線程按代碼順序執行,當執行到console.log('script start');,JS引擎主線程認爲該任務是同步任務,因此馬上執行輸出script start,而後繼續向下執行;
  2. JS引擎主線程執行到setTimeout(function() { console.log('setTimeout'); }, 0);,JS引擎主線程認爲setTimeout是異步任務API,則向瀏覽器內核進程申請開啓定時器線程進行計時和控制該setTimeout任務。因爲W3C在HTML標準中規定setTimeout低於4ms的時間間隔算爲4ms,那麼當計時到4ms時,定時器線程就把該回調處理函數推動任務隊列中等待主線程執行,而後JS引擎主線程繼續向下執行;
  3. JS引擎主線程執行到console.log('script end');,JS引擎主線程認爲該任務是同步任務,因此馬上執行輸出script end;
  4. JS引擎主線程上的任務執行完畢(輸出script start和script end)後,主線程空閒,則開始讀取任務隊列中的事件任務,將該任務隊裏的事件任務推動主線程中,按任務隊列順序執行,最終輸出setTimeout,因此輸出的結果順序爲script start script end setTimeout;

若是還不清楚,能夠看看下圖:性能

圖片描述
首先,這是一個瀏覽器環境,其中主線程操做堆和執行棧,而RunTime中存在着許多web API,當主線程讀取到setTimeOut等API時,它會交給其餘線程來處理(setTimeOut則是定時器觸發線程),定時器觸發線程會先將setTimeOut中的回調函數存放在event table中,當知足觸發條件時(如上面的4ms),就將回調函數推入事件隊列(callback queue)中,等待主線程空閒(執行棧中爲空),回調函數則被推入執行棧中進行執行。

執行上下文

執行上下文可理解爲當前的執行環境,與該運行環境相對應。js引擎每進入一個環境就會建立相應的執行上下文,建立執行上下文的過程當中,主要作了如下三件事件,如圖:

圖片描述

其中,變量對象VO(Variable object)用於存放聲明後的變量、函數和形參。咱們舉一個例子來講:

var a = 10;
 
function test(x) {
  var b = 20;
};
 
test(30);

對應的變量對象是:

// 全局上下文的變量對象
VO(global) = {
  a: undefined,
  test: <reference to function>
  //<reference to function>是test函數位於堆中的地址
};
 
// test函數上下文的變量對象
VO(test) = {
  arguments: {
      x:undefined,
      length:1
  },
  b: undefined
};

當預編譯結束,js進入解釋執行階段時,VO就會轉化爲AO(Active object),也就是活動對象。AO中變量和參數的值再也不是undefined,它們的值會隨着js的逐步執行而發生變化。

做用域鏈用於代表上下文的執行順序。上例中的做用域鏈爲:

scopeChain: [VO(test),  AO(global)],
  • 咱們這裏直接使用數組表示做用域鏈,做用域鏈的活動對象或變量對象能夠直接理解爲做用域;
  • 它的第一項永遠是當前做用域(當前上下文的變量對象或活動對象);
  • 最後一項永遠是全局做用域(全局執行上下文的活動對象);
  • 做用域鏈保證了變量和函數的有序訪問,查找方式是沿着做用域鏈從左至右查找變量或函數,找到則會中止查找,找不到則一直查找到全局做用域,再找不到則會拋出引用錯誤。

this指向當前做用域。這裏不作過多分析。

執行的三個階段

js的執行分爲三個階段:

  1. 語法分析階段:<script>標籤加載即開始語法分析,分析整個標籤內的語法錯誤。無錯誤即進入預編譯階段。
  2. 預編譯階段: 每進入一個新環境,就進行一次預編譯,同時建立一個執行上下文(包含VO對象、做用域鏈、this,忘了是什麼就往回看看)並放入執行棧中。此時,當前環境會進行必定的函數提高和變量提高,注意:函數提高優先於變量提高。環境中沒有新的函數聲明則進入解釋執行階段。
  3. 解釋執行階段: 將當前執行上下文中的VO->AO, 此後,js引擎在當前環境從上到下、從左到右執行代碼,不斷改變AO中的變量等內容。當前上下文執行完畢則出棧,執行下一個上下文。

一樣,咱們舉一個例子進行分析:

var a = 1;
function bar() {
    var b = 2;

    function foo() {
        var c = 3;
    }

    foo();
    console.log(b)
}
function coo() {
    alert("hello")
}

bar()
coo()

咱們解釋一下以上過程:

  1. 瀏覽器加載script標籤;
  2. 語法分析;
  3. 預編譯(全局),js進入全局函數環境,主線程建立全局執行上下文(Global EC),入棧;
  4. 全局執行,a=1;
  5. bar()調用,js進入bar函數環境,建立bar執行上下文(bar EC),入棧;
  6. bar執行,bar EC中VO(bar)->AO(bar),b=2;
  7. foo()調用,建立foo執行上下文(foo EC),入棧;
  8. foo執行,foo EC中VO(foo)->AO(foo),c=3;
  9. foo執行結束,出棧;
  10. 繼續執行bar,console.log(b);
  11. bar執行結束,出棧;
  12. coo()調用;
  13. ...
  14. coo執行結束,出棧;
  15. 瀏覽器或者該標籤頁關閉,Global EC出棧;


至此,你已基本理解了js的執行機制。

參考文獻:

js引擎的執行過程

深刻理解JavaScript系列(12):變量對象(Variable Object)

相關文章
相關標籤/搜索