【JS深淵】幹它!必定要完全弄懂javascript執行機制(一)

前言

【JS深淵】系列是我用來對前端原生javascript語言相關技術進行的深度探究或者筆記記錄,我我的是一個很愛去挖層的這麼一我的,但不幸的是覺悟的太晚,但我不會放棄。決定開始寫博客目的有兩個,一個是有儀式性的去持續提升本身的技術能力,另外一個就是但願向上再向上。不過本身能力也是有限的,若是寫的很差或者出現什麼紕漏又或者錯誤的地方,脫褲式歡迎你們的建議、指正。沒錯看着我,對,盯着你的屏幕,別眨眼,請記住個人這句話:
您的反饋是就是我持續進步的動力
好了,收!biu~javascript

正文

javascrip是一個弱類型解釋性語言,依賴於瀏覽器JS引擎進行解析執行。裏面的解析過程也十分複雜。可是稍微瞭解一下,對javascript語言的掌握有着很大的幫助,可以使咱們在平常的開發過程當中更加靈活運用js語言自己的特性。廢話很少說了,開始:🤣🤣html

lotoze我先來段風騷的代碼前端

console.log("123")
console.log("456");
複製代碼

某某俠士:「喂,糟老頭!你在逗我玩?「,哪裏風騷了,不就是兩句打印嗎? java

擦擦鼻血,整整發型。略顯正經起身後:「這位俠士,別急別急,請聽老頭我細細道來「。先來個問題:雖然是兩句簡單的打印語句,可是我想問的是 從在瀏覽器打開此html文件,針對於js代碼打印輸出到控制檯結果,這之間發生了什麼?
:上面以及下面的所涉及的js代碼均在此html文件中測試執行,html代碼爲:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Test</title>
</head>
<body>
<script type="text/javascript">
    //js代碼塊...
</script>
</body>
</html>
複製代碼

過程一:語法分析(代碼通讀)

這一過程,專業一點叫作語法分析,每一個計算機語言都會有這一步,並且計算機語言的區分的奧妙就在語法分析、詞法分析、語義分析的差別。不過這裏只說語法分析,能夠理解爲對加載好的代碼的一次的初次檢驗。
首先,系統,不,確切的說是js引擎線程並不會當即執行文件中的js代碼,而是會先通讀一遍js代碼,看看是否有低級的語法錯誤,若是有低級語法錯誤,那麼就當即中止並拋出錯誤到控制檯;若是沒有低級的語法錯誤,就會爲接下來js代碼的真正執行作一些準備工做。可是這樣說真的對嗎?必定要注意理解! 咱們來看一下代碼:數組

console.log("123");
console.log"456");
console.log("789");
複製代碼

咱們先來假設一下:按照剛纔咱們所說的js代碼並不會當即執行,而是會先進行通讀一遍代碼,若是有低級錯誤就拋出,並當即中止後續的代碼。那麼按照這種理解,最後的結果就是:直接拋出低級錯誤,"123"並不會輸出。可是你會驚奇的發現😱😱,"123"竟然正常輸出了,第二行報錯,後邊的不執行。最終結果爲:瀏覽器

console.log("123"); //正常打印"123"
console.log"456"); //拋出低級錯誤,當即中止解析執行
console.log("789");
複製代碼

這說明什麼?bash

說明: js引擎在通讀代碼作分析時,並非讀所有的js代碼,而是一行一行的去讀,若是這一行代碼沒有低級語法錯誤,那麼是這一行代碼進入js執行的下一準備階段,若是這一行代碼有低級語法錯誤則會在這一行拋出錯誤並當即中止。注意是 這一行數據結構

這又是爲何?爲何要一行一行的讀?閉包

由於javascript語言是解釋性的語言,是解釋一行執行一行。那麼這裏請跟lotoze去發散思考一下,解釋性語言是否是都是解釋一行執行一行呢?答案是確定的。若是是先等到所有的代碼解釋完成再同一執行,這性能得有多慢啊,黃花菜都涼了。值得一提的是,一有錯誤就會當即中止,這是單線程計算機語言的特性。異步

過程二:預編譯(執行前的準備)

預編譯是js代碼執行前一刻的準備過程。爲js的執行提供一些必要所需,在加載好的js代碼經歷過js引擎的語法分析檢驗以後會當即進入此階段。
首先,想要了解這一階段咱們須要先明確幾個重要的概念。

  • 隱式屬性

    顧名思義,隱式屬性即爲系統內部調用,不能被咱們看到或使用的屬性。

  • 做用域

    做用域分爲全局做用域和函數做用域,能夠說這兩類出現緣由是由於函數的出現,函數有着本身專屬的做用域。
    做用域有個比較專業一點的名字叫作執行期上下文。其實看着高大上的名字,本質就是一個對象,裏面存儲着js代碼執行時所須要的必要「糧食」。

    //全局做用域
    ...
    function test() {
        //局部/函數做用域
        ...
    }
    複製代碼
  • 做用域鏈

    做用域鏈專業一點的說法叫作,執行期上下文的集合按照必定的規律造成的鏈式結構叫作做用域鏈。 通俗一點講,執行期上下文穿串了,而後串太長了,成了個鏈子la😂😂

    不要衝動,大哥!
    須要注意的是,全局做用域只有一個,函數做用域每一個函數並非獨一無二的(好比閉包),但每一個函數都會有一個本身的做用域鏈。

  • 函數上的[[scopes]]隱性屬性

    咱們說,在js中的數據結構受Java的影響很大,在js中也是一切皆對象,函數也能夠看作一個對象,對象能夠擁有屬性和方法,屬性分爲顯式屬性和隱式屬性。在函數對象上都有着一個叫作[[scopes]]的隱式屬性。這個屬性的做用就是存儲着執行期上下文的集合。換句話說,[[scopes]]就是做用域鏈。它是一個棧結構,能夠理解爲是一個數組,具備棧結構First in, last out的特性。

    var b = 20;
    function test(argument) {
    	var a = 10;
    }
    複製代碼

    下面是在控制檯打印出來的test函數體,能夠看到[[scopes]]:

基本的概念已經瞭解了,可是咱們進一步思考一下,咱們說預編譯是爲js代碼真正執行階段準備過程,那麼預編譯到底準備了什麼?準備的過程是怎樣的?

*下面我來總結一下預編譯的過程,預編譯的執行過程分爲兩步:

  • 全局預編譯過程

    1. 建立Global Object對象(後面簡稱GO)。
    2. 找全局變量聲明,將全局變量名作爲GO對象的屬性名,值賦予undfined。
    3. 找全局函數聲明,將函數名做爲GO對象的屬性名,值賦予函數體。
  • 函數預編譯過程

    1. 建立Active Objct對象(後面簡稱AO)。
    2. 找局部變量或形參聲明,將局部變量或形參聲明做爲AO對象的屬性名,值賦予爲undefined。
    3. 實參與形參相統一。
    4. 找局部函數聲明,將函數名做爲AO對象的屬性名,值賦予函數體。

預編譯是變量聲明提高和函數聲明提高的根本緣由。通過預編譯過程以後,代碼將當即進入真正的執行階段。

這裏咱們來一個的栗子,來進行理解一下預編譯執行過程。

console.log(a);
console.log(b);
var a = 10;
var b = a;
function a(a) {
    var a = "aaa";
    console.log(a);
}
function foo(b) {
    var a = 100;
    var b = a;
    console.log(a);
}
foo();
a("lalala");
//請問上述代碼打印什麼?
複製代碼

這個栗子若是你只知道變量聲明提高和函數聲明提高,想要得出正確結果然的很麻煩。可是這個栗子實質上就是js代碼的預編譯過程。 咱們來分析一下:
當js代碼加載以後,通過語法分析後,進入程序運行的前一刻的預編譯階段。這個階段分爲全局預編譯階段和函數預編譯階段。 那麼咱們依據這個栗子去具體分析一下預編譯過程:

  • 全局預編譯的過程:
    1. 建立一個GO對象
      //Go
      {}
      複製代碼
    2. 找全局變量聲明,將全局變量名作爲GO對象的屬性名,值賦予undfined。
      //GO
      {
          a: undefined,
          b: undefined
      }
      複製代碼
    3. 找全局函數聲明,將函數名做爲GO對象的屬性名,值賦予函數體。.
      //GO
      {
          a: a(){}, //賦值爲函數體
          b: undefined
      }
      複製代碼
  • 函數預編譯過程
    • a函數預編譯過程
      1. 建立Active Objct對象(後面簡稱AO)。
        // aAO --> 表示a函數的AO對象
        {}
        複製代碼
      2. 找局部變量或形參聲明,將局部變量或形參聲明做爲AO對象的屬性名,值賦予爲undefined。
        //aAO --> 表示a函數的AO對象
        {
            a: undefined
        }
        複製代碼
      3. 實參與形參相統一。
        //aAO --> 表示a函數的AO對象
        {
            a: "lalala"
        }
        複製代碼
      4. 找局部函數聲明,將函數名做爲AO對象的屬性名,值賦予函數體。
        //aAO --> 表示a函數的AO對象
        {
            a: a(){}
        }
        複製代碼
    • foo函數預編譯過程
      同上,最終的foo函數的Ao對象是:
      //aAO --> 表示foo函數的AO對象
         {
             a: undefined,
             b: undefined
         }
      複製代碼

這時預編譯過程就已經完成了,生成了這個一個GO和兩個AO對象,每一個預編譯對象(虛構的,爲了理解,包括GO和AO)生成完畢的同時都會當即壓入[[scopes]]棧中,一般GO先入棧,而後是當前的AO對象入棧。下一步就是真正的執行了,調用棧則會按照先進後出的方式出棧執行。 最後的結果爲:

console.log(a); //a(){}
console.log(b); //undefined
var a = 10;
var b = a;
function a(a) {
    var a = "aaa";
    console.log(a);
}
function foo(b) {
    var a = 100;
    var b = a;
    console.log(a);
}
foo(); //100
a("lalala"); //報錯a is not a function
複製代碼

過程三:執行階段(真正執行)

js的內部執行階段,是一個很是龐雜的過程。老頭我會去使用專門的篇幅來寫。這裏只作一個簡單的介紹。
當加載的js代碼通過語法分析以及預編譯後,會進入js引擎線程會把js代碼劃分爲的宏任務(包括同步任務、異步任務)、微任務。而後按照宏任務(同步任務)-->微任務-->宏任務(異步任務)的輪詢中。

lotoze | 【原創】
着重說明:裏面一些表情圖片並不是原創,只是爲了讀者讀起來不是那麼枯燥乏味。但若是原做者以爲有侵犯版權的意思,請使用下方聯繫方式與我聯繫,爲了尊重原創做者的辛苦創做,我將及時處理!
固然,沒事也能夠聯繫啦😘😘歡迎交流!

求贊/求關注

寫做不易,
若是您還以爲湊合,就給個贊!
若是以爲確實以爲: 「老傢伙,有你的啊!」就加個關注!
若是文章有任何的錯誤,脫褲式歡迎你們來進行批評指正!
每個鼓勵都是lotoze我持續拋頭顱,撒雞血的創做動力!
每個批評反饋也都是lotoze我持續成長的臺階!

相關文章
相關標籤/搜索