經過運行機制看this綁定 、做用域、做用域鏈和閉包

1、引言

瞭解js的運行機制有助於咱們在平常的工做中,寫成高質量的代碼,減小bug的產生,節約維護成本。也有助於咱們經過造火箭的面試。 javascript

  • 瞭解JavaScript引擎。
  • 經過運行機制看做用域和做用域鏈。
  • 經過運行機制理解this的綁定和優先級。
  • 經過運行機制理解閉包。

2、渲染引擎 | JavaScript引擎(JavaScript Engine)

瞭解運行機制以前,咱們先來搞清楚幾個基本概念。css

2.1 渲染引擎

渲染是根據描述或者定義構建一個數據模型,生成圖形的過程。渲染引擎將頁面資源(html、css、javaScript等)構建成可視化、可聽化的多媒體結果。也就是咱們看到的瀏覽器網頁呈現。 html

2.2 JavaScript引擎

當咱們在運行一段代碼時,真正賦予這段代碼生命的就是JavaScript引擎。JavaScript引擎是一個專門處理JavaScript腳本的虛擬機,通常會附帶在網頁瀏覽器中。JavaScript引擎從頭至尾負責整個JavaScript程序的編譯和執行過程。java

2.2.1 JavaScript引擎有許多種

最爲你們熟知的無疑是V8引擎,他用於Chrome瀏覽器和Node中。node

  • V8 — 開源,由 Google 開發,用 C ++ 編寫。
  • Rhino — 由 Mozilla 基金會管理,開源,徹底用 Java 開發。
  • SpiderMonkey — 是第一個支持 Netscape Navigator 的 JavaScript 引擎,目前正供 Firefox 使用。
  • JavaScriptCore — 開源,以Nitro形式銷售,由蘋果爲Safari開發。
  • KJS — KDE 的引擎,最初由 Harri Porten 爲 KDE 項目中的 Konqueror 網頁瀏覽器開發。
  • Chakra (JScript9) — Internet Explorer。
  • Chakra (JavaScript) — Microsoft Edge。
  • Nashorn, 做爲 OpenJDK 的一部分,由 Oracle Java 語言和工具組編寫。
  • JerryScript —  物聯網的輕量級引擎。

2.3 渲染引擎和JavaScript引擎的關係

  • 渲染引擎經過調用接口來處理JavaScript的邏輯。
  • JavaScript經過橋接接口來訪問渲染引擎的DOM等元素。

3、JavaScript運行時(JavaScript Runtime)

若是想讓一段JavaScript代碼真正的運氣起來,單單靠JavaScript引擎是不夠的,JavaScript Engine的工做是編譯並執行 JavaScript 代碼,完成內存分配、垃圾回收等,可是缺少與外部交互的能力。面試

好比單靠一個V8引擎是沒法進行ajax請求、設置定時器、響應事件等操做的,這就須要JavaScript運行時(JavaScript Runtime)的幫助,它爲 JavaScript 提供一些對象或機制,使它可以與外界交互。ajax

好比,雖然Chrome和node都是用了V8引擎,可是他們的運行時卻不一樣,好比process、fs瀏覽器都沒法提供。瀏覽器

一段javaScript代碼的運行咱們能夠分爲兩個階段。bash

4、JavaScript運行的兩個階段

4.1 編譯階段

  • 分詞/詞法分析
  • 解析/語法分析
  • 預編譯(代碼生成、解釋階段)

4.2 執行階段

  • JavaScript並不是是簡單的一行一行解釋執行代碼,而是將JavaScript劃分爲一塊一塊的能夠執行代碼塊進行執行。JavaScript中代碼塊又分爲三種。

4.2.1 代碼塊

  • 全局能夠執行代碼。
  • 函數可執行代碼。
  • Eval可執行代碼。

接下里咱們主要說說,JavaScript的執行階段。數據結構

5、JavaScript執行

JavaScript既是編譯語言,又是解釋語言。JavaScript引擎實際上在執行代碼前僅幾微秒就編譯了代碼。

稱爲JIT(及時編譯)。它自己是一個很大的話題。可是如今,咱們能夠跳過編譯背後的理論,而只關注執行階段,這仍然頗有趣。

JavaScript引擎,編譯和解釋咱們的JavaScript代碼。JavaScript引擎其實也包含了不少較小的部分,這些較小的部分,分工合做來保證JavaScript的運行。

  • 全局內存(Global Memory)
  • 調用堆棧(Call Stack)
  • 執行上下文
  • 等等其餘組件

5.1 全局內存(Global Memory)

先看一段代碼

var num = 2;
function pow(num) {
    return num * num;
}
複製代碼

看到這段代碼,你們思考一下會發生什麼。可能你們已經想到JavaScript引擎,在執行到第一行代碼時就馬上講引用放入全局內存(Global Memory)。全局內存是JavaScript引擎保存變量和什麼函數的地方。當引擎讀取以上代碼時,全局內存將填充兩個綁定:

上面的代碼不會執行,接下來咱們嘗試執行函數。

5.2 調用棧(Call Stack)

var num = 2;
function pow(num) {
    return num * num;
}
pow(num);
複製代碼

當咱們執行函數的時,JavaScript引擎會用到調用堆棧(Call Stack)。調用堆棧是一個堆棧類的數據結構,意味着它是先進後出的執行方式。若是是多個函數,將依次進棧,先進後出。

打開瀏覽器控制檯,而後查看「來源」標籤。您將看到一些框,其中一個更有趣的名稱是Call Stack。

當代碼塊在執行時,JavaScript引擎會建立一個執行上下文,已做爲代碼運行的基礎運行環境。

6、執行上下文

在"4.2.1代碼塊",有三種代碼塊,分別對應三種執行上下文

  • 全局能夠執行代碼 => 全局執行上下文。
  • 函數可執行代碼 => 函數執行上下文。
  • Eval可執行代碼 => Eval執行上下文。

6.1 全局執行上下文

基礎執行上下文,一個程序只有一個全局執行上下文,任何不在函數內部的代碼都在全局執行執行上下文。全局執行上下文只要作兩件事情:

  • 建立一個全局的 window 對象(瀏覽器的狀況下)。
  • 置 this 的值等於這個全局對象。

6.2 函數執行上下文

若是咱們的函數有一些嵌套變量或一個或多個內部函數怎麼辦?

var num = 2;
function pow(num) {
    var a = 1,
        b = 2,
        c = 3;
    function add(a, b, c) {
        return a + b + c;
    }
}
複製代碼

每當一個函數被調用時,都會爲該函數建立一個新的上下文。每一個函數都有它本身的執行上下文,不過是在函數被調用時建立的。函數上下文能夠有任意多個。每當一個新的執行上下文被建立。

6.3 Eval執行上下文

執行在 eval 內部的代碼也會有它屬於本身的執行上下文,請不要、不要、不要輕易使用它。

執行上下文也分爲建立和執行階段。在建立階段就很是有意思了。

7、執行上下文的建立

執行上下文的建立階段主要作了三件事:

  • 決定this的綁定。
  • 建立詞法環境。
  • 建立變量環境。

7.1 this綁定

在建立可執行上下文的時候,根據代碼的執行條件,來判斷分別進行默認綁定、隱式綁定、顯示綁定等。

7.1.1 this綁定分類

  • 普通函數的調用:this指向window(瀏覽器環境)。
  • 對象方法的調用:this指向調用對象。(隱式綁定)
  • 構造函數:this指向構造函數實例。
  • apply、call、bind:this指向綁定值。(顯示綁定)
  • 箭頭函數this:this指向外層第一個普通函數調用的this。(默認綁定)

7.1.2 this綁定優先級

this綁定也是有優先級的,優先級規則以下:

  1. 函數是否存在new綁定調用:若是是的話this綁定到新建立的對象上。
  2. 函數是否經過apply、call、bind顯示綁定:若是是,this綁定到指定對象上。
  3. 函數是否在對象方法隱式調用:若是是的話,this綁定到調用對象。
  4. 若是上面三條都不知足的話:在嚴格模型下,this綁定到undefined,在非嚴格模式下,this綁定到全局對象上。

7.2 詞法環境

詞法環境是JavaScript引擎內部用來跟蹤標識符和特定變量之間的映射關係。詞法環境是Js做用域的實現機制。若是以前瞭解過做用域概念的話,和詞法環境是相似的(ES6以後做用域概念變爲詞法環境概念)。

做用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。也就是說做用域最大的用處就是隔離變量,不一樣做用域下同名變量不會有衝突。 ES6 以前 JavaScript 沒有塊級做用域,只有全局做用域和函數做用域。ES6的到來,爲咱們提供了‘塊級做用域’,可經過新增命令let和const來體現。

7.2.1 詞法環境分類

  • 全局環境:全局環境的外部環境引用是 null,它擁有內建的對象 Object/Array/等、環境記錄器內的原型函數、定義的全局變量。
  • 模塊環境:模塊環境的外部環境引用是全局環境(window,瀏覽器環境),它擁有模塊頂級聲明的綁定、模塊顯式導入的綁定。
  • 函數環境:函數環境外部引用能夠是其餘函數環境,也能夠是全局環境。它擁有聲明變量和函數。

7.2.2 詞法環境組成

  • 外部環境的引用(outer Lexical Environment):指它能夠訪問其父級詞法環境(即做用域)。
  • 環境記錄器 (Environment Record):存儲變量和函數聲明的實際位置。(聲明式環境記錄器,對象式環境記錄器是兩個比較主要環境記錄器)。

詞法環境中含有外部詞法環境的引用,咱們能夠經過這個引用獲取外部詞法環境的變量、聲明等,這些引用串聯起來一直指向全局的詞法環境,所以造成了做用域鏈。

詞法環境中含有外部詞法環境的引用,咱們能夠經過這個引用獲取外部詞法環境的變量、聲明等,所以造成了閉包。

7.3 變量環境

查看大量資料都沒有詳細的記錄變量環境。

ES5標準文檔中規定,執行環境包括:詞法環境、變量環境、this綁定。其中執行環境的詞法環境和變量環境組件始終爲詞法環境對象。當建立一個執行環境時,其詞法環境組件和變量環境組件最初是同一個值。在該執行環境相關聯的代碼的執行過程當中,變量環境組件永遠不變,而詞法環境組件有可能改變。

變量環境的不變和詞法環境的可能改變都是指引用的改變,規範12.10和12.14兩部分的內容提到了詞法環境在with以及catch語句塊中會改變。

8、JavaScript的執行過程總結

9、預覽總體過程,本文只是講了一下部分

JavaScript既是編譯語言,又是解釋語言。可是JavaScript本質上是一種解釋型語言,與編譯型語言不一樣的是它須要一邊執行一邊解析,而編譯型語言在執行時已經完成編譯,可直接執行,有更快的執行速度。

參考:

相關文章
相關標籤/搜索