亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JavaScript引擎的基本原理是什么

發布時間:2020-10-12 13:36:47 來源:億速云 閱讀:217 作者:小新 欄目:web開發

這篇文章將為大家詳細講解有關JavaScript引擎的基本原理是什么,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

JavaScript 引擎的工作流程 (pipeline)

這一切都要從你寫的 JavaScript 代碼開始。JavaScript 引擎解析源代碼并將其轉換為抽象語法樹(AST)。基于 AST,解釋器便可以開始工作并生成字節碼。就在此時,引擎開始真正地運行 JavaScript 代碼。JavaScript引擎的基本原理是什么為了讓它運行得更快,字節碼能與分析數據一起發送到優化編譯器。優化編譯器基于現有的分析數據做出某些特定的假設,然后生成高度優化的機器碼。

如果某個時刻某一個假設被證明是不正確的,那么優化編譯器將取消優化并返回到解釋器階段。

JavaScript 引擎中的解釋器/編譯器工作流程

現在,讓我們來看實際執行 JavaScript 代碼的這部分流程,即代碼被解釋和優化的部分,并討論其在主要的 JavaScript 引擎之間存在的一些差異。

一般來說,JavaSciript 引擎都有一個包含解釋器和優化編譯器的處理流程。其中,解釋器可以快速生成未優化的字節碼,而優化編譯器會耗費更長的時間,但最終可生成高度優化的機器碼。JavaScript引擎的基本原理是什么這個通用流程和 Chrome 和 Node.js 中使用的 Javascript 引擎, V8 的工作流程幾乎一致:JavaScript引擎的基本原理是什么V8 中的解釋器稱為 Ignition,負責生成和執行字節碼。當它運行字節碼時,它收集分析數據,這些數據可用于后面加快代碼的執行速度。當一個函數變為 hot 時,例如當它經常運行時,生成的字節碼和分析數據將傳遞給我們的優化編譯器 Turbofan,以根據分析數據生成高度優化的機器代碼。JavaScript引擎的基本原理是什么Mozilla 在 Firefox 和 Spidernode 中使用的 JavaScript 引擎 SpiderMonkey ,則不太一樣。它們有兩個優化編譯器,而不是一個。解釋器先通過 Baseline 編譯器,生成一些優化的代碼。然后,結合運行代碼時收集的分析數據,IonMonkey 編譯器可以生成更高程度優化的代碼。如果嘗試優化失敗,IonMonkey 將返回到 Baseline 階段的代碼。

Chakra,在 Edge 中使用的 Microsoft 的 JavaScript 引擎,非常相似的,也有2個優化編譯器。解釋器優化代碼到 SimpleJIT(JIT 代表 Just-In-Time 編譯器,即時編譯器),SimpleJIT 會生成稍微優化的代碼。而 FullJIT 結合分析數據,可以生成更為優化的代碼。JavaScript引擎的基本原理是什么JavaScriptCore(縮寫為 JSC),在 Safari 和 React Native 中使用的 Apple 的 JavaScript 引擎,它通過三種不同的優化編譯器將其發揮到極致。低層解釋器 LLInt 優化代碼到 Baseline 編譯器中,然后優化代碼到 DFG(Data Flow Graph)編譯器中,DFG(Data Flow Graph)編譯器又可以將優化后的代碼傳到 FTL(Faster Than Light)編譯器中。

為什么有些引擎有更多的優化編譯器?這是權衡利弊的結果。解釋器可以快速生成字節碼,但字節碼通常效率不高。另一方面,優化編譯器需要更長的時間,但最終會產生更高效的機器代碼。在快速讓代碼運行(解釋器)或花費更多時間,但最終以最佳性能運行代碼(優化編譯器)之間需要權衡。一些引擎選擇添加具有不同時間/效率特性的多個優化編譯器,允許在額外的復雜性的代價下對這些權衡進行更細粒度的控制。另一個需要權衡的方面與內存使用有關,后續會有專門的文章詳細介紹。

我們剛剛強調了每個 JavaScript 引擎中解釋器和優化編譯器流程中的主要差異。除了這些差異之外,在高層上,所有 JavaScript 引擎都有相同的架構:那就是有一個解析器和某種解釋器/編譯器流程。

JavaScript 的對象模型

讓我們通過放大一些方面的實現來看看 JavaScript 引擎還有什么共同點。

例如,JavaScript 引擎如何實現 JavaScript 對象模型,以及它們使用哪些技巧來加速訪問 JavaScript 對象的屬性?事實證明,所有主要引擎在這一點上的實現都很相似。

ECMAScript 規范基本上將所有對象定義為由字符串鍵值映射到 property 屬性的字典。

JavaScript引擎的基本原理是什么除了 [[Value]] 本身,規范還定義了這些屬性:

  • [[Writable]] 決定該屬性是否能被重新賦值,
  • [[Enumerable]] 決定屬性是否出現在 for in 循環中,
  • [[Configurable]] 決定屬性是否能被刪除。

[[雙方括號]] 的符號表示看上去有些特別,但這正是規范定義不能直接暴露給 JavaScript 的屬性的表示方法。在 JavaScript 中你仍然可以通過 Object.getOwnPropertyDescriptor API 獲得指定對象的屬性值:

const object = { foo: 42 };Object.getOwnPropertyDescriptor(object, 'foo');// → { value: 42, writable: true, enumerable: true, configurable: true }復制代碼

這就是 JavaScript 定義對象的方式,那么數組呢?

你可以把數組看成是一個特殊的對象,其中的一個區別就是數組會對數組索引進行特殊的處理。這里的數組索引是 ECMAScript 規范中的一個特殊術語。在 JavaScript 中限制數組最多有 232?1個元素,數組索引是在該范圍內的任何有效索引,即 0 到 232?2 的任何整數。

另一個區別是數組還有一個特殊的 length 屬性。

const array = ['a', 'b'];
array.length; // → 2array[2] = 'c';
array.length; // → 3復制代碼

在該例中,數組被創建時 length 為 2。當我們給索引為 2 的位置分配另一個元素時,length 自動更新了。

JavaScript 定義數組的方式和對象類似。例如,所有的鍵值, 包括數組的索引, 都明確地表示為字符串。數組中的第一個元素,就是存儲在鍵值 '0' 下。JavaScript引擎的基本原理是什么“length” 屬性是另一個不可枚舉且不可配置的屬性。 當一個元素被添加到數組中時, JavaScript 會自動更新 “length“ 屬性的 [[value]] 屬性。JavaScript引擎的基本原理是什么

優化屬性訪問

知道了對象在 JavaScript 中是如何定義的, 那么就讓我們來深入地了解一下 JavaScript 引擎是如何高效地使用對象的。 總體來說,訪問屬性是至今為止 JavaScript 程序中最常見的操作。因此,JavaScript 引擎是否能快速地訪問屬性是至關重要的。

Shapes

在 JavaScript 程序中,多個對象有相同的鍵值屬性是非常常見的。可以說,這些對象有相同的 shape。

const object1 = { x: 1, y: 2 };const object2 = { x: 3, y: 4 };// object1 and object2 have the same shape.復制代碼

訪問擁有相同 shape 的對象的相同屬性也是非常常見的:

function logX(object) {    console.log(object.x);
}const object1 = { x: 1, y: 2 };const object2 = { x: 3, y: 4 };

logX(object1);
logX(object2);復制代碼

考慮到這一點,JavaScript 引擎可以基于對象的 shape 來優化對象的屬性訪問。下面我們就來介紹其原理。

假設我們有一個具有屬性 x 和 y 的對象,它使用我們前面討論過的字典數據結構:它包含字符串形式的鍵,這些鍵指向它們各自的屬性值。JavaScript引擎的基本原理是什么

如果你訪問某個屬性,例如 object.y,JavaScript 引擎會在 JSObject 中查找鍵值 'y',然后加載相應的屬性值,最后返回 [[Value]]。

但這些屬性值存儲在內存中的什么位置呢?我們是否應該將它們作為 JSObject 的一部分進行存儲?假設我們稍后會遇到更多同 shape 的對象,那么在 JSObject 自身存儲包含屬性名和屬性值的完整字典便是一種浪費,因為對于具有相同 shape 的所有對象,屬性名都是重復的。 這是大量的重復和不必要的內存使用。 作為一種優化,引擎將對象的 Shape 分開存儲。JavaScript引擎的基本原理是什么shape 包含除了 [[Value]] 以外所有屬性名和屬性。另外,shape 還包含了 JSObject 內部值的偏移量,以便 JavaScript 引擎知道在哪里查找值。具有相同 shape 的每個 JSObject 都指向該 shape 實例。現在每個 JSObject 只需要存儲對這個對象來說唯一的值。JavaScript引擎的基本原理是什么當我們有多個對象時,好處就顯而易見了。不管有多少個對象,只要它們有相同的 shape,我們只需要存儲 shape 和屬性信息一次!

所有的 JavaScript 引擎都使用了 shapes 作為優化,但稱呼各有不同:

  • 學術論文稱它們為 Hidden Classes(容易與 JavaScript 中的 class 混淆)
  • V8 稱它們為 Maps (容易與 JavaScript 中的 Map 混淆)
  • Chakra 稱它們為 Types (容易與 JavaScript 中的動態類型以及 typeof 混淆)
  • JavaScriptCore 稱它們為 Structures
  • SpiderMonkey 稱它們為 Shapes

本文中,我們將繼續使用術語 shapes.

轉換鏈和樹

如果你有一個具有特定 shape 的對象,但你又向它添加了一個屬性,此時會發生什么? JavaScript 引擎是如何找到這個新 shape 的?

const object = {};
object.x = 5;
object.y = 6;復制代碼

這些 shapes 在 JavaScript 引擎中形成所謂的轉換鏈(transition chains)。下面是一個例子:

JavaScript引擎的基本原理是什么

該對象開始沒有任何屬性,因此它指向一個空的 shape。下一個語句為該對象添加一個值為 5 的屬性 "x",所以 JavaScript 引擎轉向一個包含屬性 "x" 的 shape,并在第一個偏移量為 0 處向 JSObject 添加了一個值 5。 下一行添加了一個屬性 'y',引擎便轉向另一個包含 'x' 和 'y' 的 shape,并將值 6 添加到 JSObject(位于偏移量 1 處)。

我們甚至不需要為每個 shape 存儲完整的屬性表。相反,每個shape 只需要知道它引入的新屬性。例如,在本例中,我們不必將有關 “x” 的信息存儲在最后一個 shape 中,因為它可以在更早的鏈上找到。要實現這一點,每個 shape 都會鏈接回其上一個 shape:JavaScript引擎的基本原理是什么

如果你在 JavaScript 代碼中寫 o.x,JavaScript 引擎會沿著轉換鏈去查找屬性 "x",直到找到引入屬性 "x" 的 Shape。

但是如果沒有辦法創建一個轉換鏈會怎么樣呢?例如,如果有兩個空對象,并且你為每個對象添加了不同的屬性,該怎么辦?

const object1 = {};
object1.x = 5;const object2 = {};
object2.y = 6;復制代碼

在這種情況下,我們必須進行分支操作,最終我們會得到一個轉換樹而不是轉換鏈。JavaScript引擎的基本原理是什么

這里,我們創建了一個空對象 a,然后給它添加了一個屬性 ‘x’。最終,我們得到了一個包含唯一值的 JSObject 和兩個 Shape :空 shape 以及只包含屬性 x 的 shape。

第二個例子也是從一個空對象 b 開始的,但是我們給它添加了一個不同的屬性 ‘y’。最終,我們得到了兩個 shape 鏈,總共 3 個 shape。

這是否意味著我們總是需要從空 shape 開始呢? 不一定。引擎對已含有屬性的對象字面量會進行一些優化。比方說,我們要么從空對象字面量開始添加 x 屬性,要么有一個已經包含屬性 x 的對象字面量:

const object1 = {};
object1.x = 5;const object2 = { x: 6 };復制代碼

在第一個例子中,我們從空 shape 開始,然后轉到包含 x 的shape,這正如我們之前所見那樣。

在 object2 的例子中,直接在一開始就生成含有 x 屬性的對象,而不是生成一個空對象是有意義的。JavaScript引擎的基本原理是什么

包含屬性 ‘x’ 的對象字面量從含有 ‘x’ 的 shape 開始,有效地跳過了空 shape。V8 和 SpiderMonkey (至少)正是這么做的。這種優化縮短了轉換鏈并且使從字面量構建對象更加高效。

下面是一個包含屬性 ‘x'、'y' 和 'z' 的 3D 點對象的示例。

const point = {};
point.x = 4;
point.y = 5;
point.z = 6;復制代碼

正如我們之前所了解的, 這會在內存中創建一個有3個 shape 的對象(不算空 shape 的話)。 當訪問該對象的屬性 ‘x’ 的時候,比如, 你在程序里寫 point.x,javaScript 引擎需要循著鏈接列表尋找:它會從底部的 shape 開始,一層層向上尋找,直到找到頂部包含 ‘x’ 的 shape。JavaScript引擎的基本原理是什么

當這樣的操作更頻繁時, 速度會變得非常慢,特別是當對象有很多屬性的時候。尋找屬性的時間復雜度為 O(n), 即和對象上的屬性數量線性相關。為了加快屬性的搜索速度, JavaScript 引擎增加了一種 ShapeTable 的數據結構。這個 ShapeTable 是一個字典,它將屬性鍵映射到描述對應屬性的 shape 上。

JavaScript引擎的基本原理是什么

現在我們又回到字典查找了我們添加 shape 就是為了對此進行優化!那我們為什么要去糾結 shape 呢? 原因是 shape 啟用了另一種稱為 Inline Caches 的優化。

Inline Caches (ICs)

shapes 背后的主要動機是 Inline Caches 或 ICs 的概念。ICs 是讓 JavaScript 快速運行的關鍵要素!JavaScript 引擎使用 ICs 來存儲查找到對象屬性的位置信息,以減少昂貴的查找次數。

這里有一個函數 getX,該函數接收一個對象并從中加載屬性 x:

function getX(o) {    return o.x;
}復制代碼

如果我們在 JSC 中運行該函數,它會產生以下字節碼:JavaScript引擎的基本原理是什么

第一條 get_by_id 指令從第一個參數(arg1)加載屬性 ‘x’,并將結果存儲到 loc0 中。第二條指令將存儲的內容返回給 loc0。

JSC 還將一個 Inline Cache 嵌入到 get_by_id 指令中,該指令由兩個未初始化的插槽組成。

JavaScript引擎的基本原理是什么現在, 我們假設用一個對象 { x: 'a' },來執行 getX 這個函數。正如我們所知,,這個對象有一個包含屬性 ‘x’ 的 shape, 該 shape存儲了屬性 ‘x’ 的偏移量和特性。當你在第一次執行這個函數的時候,get_by_id 指令會查找屬性 ‘x’,然后發現其值存儲在偏移量為 0 的位置。

JavaScript引擎的基本原理是什么

嵌入到 get_by_id 指令中的 IC 存儲了 shape 和該屬性的偏移量:

JavaScript引擎的基本原理是什么

對于后續運行,IC 只需要比較 shape,如果 shape 與之前相同,只需從存儲的偏移量加載值。具體來說,如果 JavaScript 引擎看到對象的 shape 是 IC 以前記錄過的,那么它根本不需要接觸屬性信息,相反,可以完全跳過昂貴的屬性信息查找過程。這要比每次都查找屬性快得多。

高效存儲數組

對于數組,存儲數組索引屬性是很常見的。這些屬性的值稱為數組元素。為每個數組中的每個數組元素存儲屬性特性是非常浪費內存的。相反,默認情況下,數組索引屬性是可寫的、可枚舉的和可配置的,JavaScript 引擎基于這一點將數組元素與其他命名屬性分開存儲。

思考下面的數組:

const array = [    '#jsconfeu',
];復制代碼

引擎存儲了數組長度(1),并指向包含偏移量和 'length' 屬性特性的 shape。

JavaScript引擎的基本原理是什么

這和我們之前看到的很相似……但是數組的值存到哪里了呢?

JavaScript引擎的基本原理是什么

每個數組都有一個單獨的元素備份存儲區,包含所有數組索引的屬性值。JavaScript 引擎不必為數組元素存儲任何屬性特性,因為它們通常都是可寫的、可枚舉的和可配置的。

那么,在非通常情況下會怎么樣呢?如果更改了數組元素的屬性特性,該怎么辦?

// Please don’t ever do this!const array = Object.defineProperty(
    [],    '0',
    {        
        value: 'Oh noes!!1',        
        writable: false,        
        enumerable: false,        
        configurable: false,
    });復制代碼

上面的代碼片段定義了名為 “0” 的屬性(恰好是數組索引),但將其特性設置為非默認值。

在這種邊緣情況下,JavaScript 引擎將整個元素備份存儲區表示成一個字典,該字典將數組索引映射到屬性特性。

JavaScript引擎的基本原理是什么

即使只有一個數組元素具有非默認特性,整個數組的備份存儲區也會進入這種緩慢而低效的模式。避免對數組索引使用Object.defineProperty!

關于JavaScript引擎的基本原理是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

庆城县| 汝阳县| 岳阳市| 长岭县| 宝兴县| 肥乡县| 乳山市| 常熟市| 轮台县| 东山县| 屏边| 高唐县| 双江| 崇明县| 防城港市| 临洮县| 唐海县| 灌云县| 邓州市| 京山县| 建阳市| 鄄城县| 瑞昌市| 闸北区| 沙河市| 察雅县| 翁源县| 太仓市| 台南县| 开封县| 晋州市| 贵阳市| 安达市| 大荔县| 霍邱县| 辽宁省| 通江县| 沁源县| 元氏县| 庆云县| 岐山县|