<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【JS】1046- 萬字干貨!詳解JavaScript執(zhí)行過程

          共 14735字,需瀏覽 30分鐘

           ·

          2021-08-15 23:56

          js代碼的執(zhí)行,主要分為兩個個階段:編譯階段、執(zhí)行階段!本文所有內(nèi)容基于V8引擎。

          1前言

          v8引擎

          v8引擎工作原理:

          V8由許多子模塊構成,其中這4個模塊是最重要的:

          • Parser:負責將JavaScript源碼轉(zhuǎn)換為Abstract Syntax Tree (AST);
            • 如果函數(shù)沒有被調(diào)用,那么是不會被轉(zhuǎn)換成AST的
          • Ignition:interpreter,即解釋器,負責將AST轉(zhuǎn)換為Bytecode,解釋執(zhí)行Bytecode;同時收集TurboFan優(yōu)化編譯所需的信息,比如函數(shù)參數(shù)的類型,有了類型才能進行真實的運算;
            • 如果函數(shù)只調(diào)用一次,Ignition會執(zhí)行解釋執(zhí)行ByteCode
            • 解釋器也有解釋執(zhí)行bytecode的能力

          通常有兩種類型的解釋器,基于棧 (Stack-based)基于寄存器 (Register-based),基于棧的解釋器使用棧來保存函數(shù)參數(shù)、中間運算結果、變量等;基于寄存器的虛擬機則支持寄存器的指令操作,使用寄存器來保存參數(shù)、中間計算結果。通常,基于棧的虛擬機也定義了少量的寄存器,基于寄存器的虛擬機也有堆棧,其區(qū)別體現(xiàn)在它們提供的指令集體系。大多數(shù)解釋器都是基于棧的,比如 Java 虛擬機.Net 虛擬機,還有早期的 V8 虛擬機。基于堆棧的虛擬機在處理函數(shù)調(diào)用、解決遞歸問題和切換上下文時簡單明快。而現(xiàn)在的 V8 虛擬機則采用了基于寄存器的設計,它將一些中間數(shù)據(jù)保存到寄存器中。基于寄存器的解釋器架構:

          • TurboFan:compiler,即編譯器,利用Ignitio所收集的類型信息,將Bytecode轉(zhuǎn)換為優(yōu)化的匯編代碼;
            • 如果一個函數(shù)被多次調(diào)用,那么就會被標記為熱點函數(shù),那么就會經(jīng)過TurboFan轉(zhuǎn)換成優(yōu)化的機器碼,提高代碼的執(zhí)行性能;

            • 但是,機器碼實際上也會被還原為ByteCode,這是因為如果后續(xù)執(zhí)行函數(shù)的過程中,類型發(fā)生了變化(比如sum函數(shù)原來執(zhí)行的是number類型,后來執(zhí)行變成了string類型),之前優(yōu)化的機器碼并不能正確的處理運算,就會逆向的轉(zhuǎn)換成字節(jié)碼;

          • Orinoco:garbage collector,垃圾回收模塊,負責將程序不再需要的內(nèi)存空間回收;

          提一嘴

          棧 stack

          棧的特點是"LIFO,即后進先出(Last in, first out)"。數(shù)據(jù)存儲時只能從頂部逐個存入,取出時也需從頂部逐個取出。

          堆 heap

          堆的特點是"無序"的key-value"鍵值對"存儲方式。堆的存取方式跟順序沒有關系,不局限出入口。

          隊列 queue

          隊列的特點是是"FIFO,即先進先出(First in, first out)" 。數(shù)據(jù)存取時"從隊尾插入,從隊頭取出"。

          "與棧的區(qū)別:棧的存入取出都在頂部一個出入口,而隊列分兩個,一個出口,一個入口"。

          2編譯階段

          詞法分析 Scanner

          將由字符組成的字符串分解成(對編程語言來說)有意義的代碼塊,這些代碼塊被稱為詞法單元(token)。

          [
              {
                  "type""Keyword",
                  "value""var"
              },
              {
                  "type""Identifier",
                  "value""name"
              },
              {
                  "type""Punctuator",
                  "value""="
              },
              {
                  "type""String",
                  "value""'finget'"
              },
              {
                  "type""Punctuator",
                  "value"";"
              }
          ]

          語法分析 Parser

          這個過程是將詞法單元流(數(shù)組)轉(zhuǎn)換成一個由元素逐級嵌套所組成的代表了程序語法結構的樹。這個樹被稱為“抽象語法樹”(Abstract Syntax Tree,AST)。

          {
          "type": "Program",
          "body": [
          {
          "type": "VariableDeclaration",
          "declarations": [
          {
          "type": "VariableDeclarator",
          "id": {
          "type": "Identifier",
          "name": "name"
          },
          "init": {
          "type": "Literal",
          "value": "finget",
          "raw": "'finget'"
          }
          }
          ],
          "kind": "var"
          }
          ],
          "sourceType": "script"
          }

          在此過程中,如果源代碼不符合語法規(guī)則,則會終止,并拋出“語法錯誤”。

          這里有個工具,可以實時生成語法樹,可以試試esprima。

          字節(jié)碼生成

          可以用node node --print-bytecode查看字節(jié)碼:

          // test.js
          function getMyname({
           var myname = 'finget';
           console.log(myname);
          }
          getMyname();
          node --print-bytecode test.js 

          ...
          [generated bytecode for function: getMyname (0x10ca700104e9 <SharedFunctionInfo getMyname>)]
          Parameter count 1
          Register count 3
          Frame size 24
          18 E> 0x10ca70010e86 @ 0 : a7 StackCheck
          37 S> 0x10ca70010e87 @ 1 : 12 00 LdaConstant [0]
          0x10ca70010e89 @ 3 : 26 fb Star r0
          48 S> 0x10ca70010e8b @ 5 : 13 01 00 LdaGlobal [1], [0]
          0x10ca70010e8e @ 8 : 26 f9 Star r2
          56 E> 0x10ca70010e90 @ 10 : 28 f9 02 02 LdaNamedProperty r2, [2], [2]
          0x10ca70010e94 @ 14 : 26 fa Star r1
          56 E> 0x10ca70010e96 @ 16 : 59 fa f9 fb 04 CallProperty1 r1, r2, r0, [4]
          0x10ca70010e9b @ 21 : 0d LdaUndefined
          69 S> 0x10ca70010e9c @ 22 : ab Return
          Constant pool (size = 3)
          Handler Table (size = 0)
          ...

          這里涉及到一個很重要的概念:JIT(Just-in-time)一邊解釋,一邊執(zhí)行。

          它是如何工作的呢(結合第一張流程圖來看):

          1.在 JavaScript 引擎中增加一個監(jiān)視器(也叫分析器)。監(jiān)視器監(jiān)控著代碼的運行情況,記錄代碼一共運行了多少次、如何運行的等信息,如果同一行代碼運行了幾次,這個代碼段就被標記成了 “warm”,如果運行了很多次,則被標記成 “hot”;

          2.(基線編譯器)如果一段代碼變成了 “warm”,那么 JIT 就把它送到基線編譯器去編譯,并且把編譯結果存儲起來。比如,監(jiān)視器監(jiān)視到了,某行、某個變量執(zhí)行同樣的代碼、使用了同樣的變量類型,那么就會把編譯后的版本,替換這一行代碼的執(zhí)行,并且存儲;

          3.(優(yōu)化編譯器)如果一個代碼段變得 “hot”,監(jiān)視器會把它發(fā)送到優(yōu)化編譯器中。生成一個更快速和高效的代碼版本出來,并且存儲。例如:循環(huán)加一個對象屬性時,假設它是 INT 類型,優(yōu)先做 INT 類型的判斷;

          4.(反優(yōu)化 Deoptimization)可是對于 JavaScript 從來就沒有確定這么一說,前 99 個對象屬性保持著 INT 類型,可能第 100 個就沒有這個屬性了,那么這時候 JIT 會認為做了一個錯誤的假設,并且把優(yōu)化代碼丟掉,執(zhí)行過程將會回到解釋器或者基線編譯器,這一過程叫做反優(yōu)化。

          作用域

          作用域是一套規(guī)則,用來管理引擎如何查找變量。在es5之前,js只有全局作用域函數(shù)作用域。es6引入了塊級作用域。但是這個塊級別作用域需要注意的是不是{}的作用域,而是letconst關鍵字的塊級作用域

          var name = 'FinGet';

          function fn({
            var age = 18;
            console.log(name);
          }

          在解析時就會確定作用域:

          簡單的來說,作用域就是個盒子,規(guī)定了變量和函數(shù)的可訪問范圍以及他們的生命周期。

          詞法作用域

          詞法作用域就是指作用域是由代碼中函數(shù)聲明的位置來決定的,所以詞法作用域是靜態(tài)的作用域,通過它就能夠預測代碼在執(zhí)行過程中如何查找標識符。

          function fn({
              console.log(myName)
          }
          function fn1({
              var myName = " FinGet "
              fn()
          }
          var myName = " global_finget "
          fn1()

          上面代碼打印的結果是:global_finget,這就是因為在編譯階段就已經(jīng)確定了作用域,fn是定義在全局作用域中的,它在自己內(nèi)部找不到myName就會去全局作用域中找,不會在fn1中查找。

          3執(zhí)行階段

          執(zhí)行上下文

          遇到函數(shù)執(zhí)行的時候,就會創(chuàng)建一個執(zhí)行上下文。執(zhí)行上下文是當前 JavaScript 代碼被解析和執(zhí)行時所在環(huán)境的抽象概念。

          JavaScript 中有三種執(zhí)行上下文類型:

          • 全局執(zhí)行上下文 (只有一個)
          • 函數(shù)執(zhí)行上下文
          • eval

          執(zhí)行上下文的創(chuàng)建分為兩個階段創(chuàng)建:1.創(chuàng)建階段 2.執(zhí)行階段

          創(chuàng)建階段

          在任意的 JavaScript 代碼被執(zhí)行時,執(zhí)行上下文處于創(chuàng)建階段。在創(chuàng)建階段中總共發(fā)生了三件事情:

          • 確定 this 的值,也被稱為 This Binding
          • LexicalEnvironment(詞法環(huán)境) 組件被創(chuàng)建。
          • VariableEnvironment(變量環(huán)境) 組件被創(chuàng)建。
          ExecutionContext = {  
            ThisBinding = <this value>,     // 確定this 
            LexicalEnvironment = { ... },   // 詞法環(huán)境
            VariableEnvironment = { ... },  // 變量環(huán)境
          }
          This Binding

          在全局執(zhí)行上下文中,this 的值指向全局對象,在瀏覽器中,this 的值指向 window 對象。在函數(shù)執(zhí)行上下文中,this 的值取決于函數(shù)的調(diào)用方式。如果它被一個對象引用調(diào)用,那么 this 的值被設置為該對象,否則 this 的值被設置為全局對象或 undefined(嚴格模式下)。

          詞法環(huán)境(Lexical Environment)

          詞法環(huán)境是一個包含標識符變量映射的結構。(這里的標識符表示變量/函數(shù)的名稱,變量是對實際對象【包括函數(shù)類型對象】或原始值的引用)。在詞法環(huán)境中,有兩個組成部分:(1)環(huán)境記錄(environment record) (2)對外部環(huán)境的引用

          • 環(huán)境記錄是存儲變量函數(shù)聲明的實際位置。
          • 對外部環(huán)境的引用意味著它可以訪問其外部詞法環(huán)境。(實現(xiàn)作用域鏈的重要部分)

          詞法環(huán)境有兩種類型:

          • 全局環(huán)境(在全局執(zhí)行上下文中)是一個沒有外部環(huán)境的詞法環(huán)境。全局環(huán)境的外部環(huán)境引用為 null。它擁有一個全局對象(window 對象)及其關聯(lián)的方法和屬性(例如數(shù)組方法)以及任何用戶自定義的全局變量,this 的值指向這個全局對象。

          • 函數(shù)環(huán)境,用戶在函數(shù)中定義的變量被存儲在環(huán)境記錄中。對外部環(huán)境的引用可以是全局環(huán)境,也可以是包含內(nèi)部函數(shù)的外部函數(shù)環(huán)境。

          注意:對于函數(shù)環(huán)境而言,環(huán)境記錄 還包含了一個 arguments 對象,該對象包含了索引和傳遞給函數(shù)的參數(shù)之間的映射以及傳遞給函數(shù)的參數(shù)的長度(數(shù)量)。

          變量環(huán)境 Variable Environment

          它也是一個詞法環(huán)境,其 EnvironmentRecord 包含了由 VariableStatements 在此執(zhí)行上下文創(chuàng)建的綁定。

          如上所述,變量環(huán)境也是一個詞法環(huán)境,因此它具有上面定義的詞法環(huán)境的所有屬性。

          示例代碼:

          let a = 20;  
          const b = 30;  
          var c;

          function multiply(e, f{  
           var g = 20;  
           return e * f * g;  
          }

          c = multiply(2030);

          執(zhí)行上下文:

          GlobalExectionContext = {

            ThisBinding: <Global Object>,

            LexicalEnvironment: {  
              EnvironmentRecord: {  
                Type: "Object",  
                // 標識符綁定在這里  
                a: < uninitialized >,  
                b: < uninitialized >,  
                multiply: < func >  
              }  
              outer: <null>  
            },

            VariableEnvironment: {  
              EnvironmentRecord: {  
                Type: "Object",  
                // 標識符綁定在這里  
                c: undefined,  
              }  
              outer: <null>  
            }  
          }

          FunctionExectionContext = {  
             
            ThisBinding: <Global Object>,

            LexicalEnvironment: {  
              EnvironmentRecord: {  
                Type: "Declarative",  
                // 標識符綁定在這里  
                Arguments: {0: 20, 1: 30, length: 2},  
              },  
              outer: <GlobalLexicalEnvironment>  // 指定全局環(huán)境
            },

            VariableEnvironment: {  
              EnvironmentRecord: {  
                Type: "Declarative",  
                // 標識符綁定在這里  
                g: undefined  
              },  
              outer: <GlobalLexicalEnvironment>  
            }  
          }

          仔細看上面的:a: < uninitialized >,c: undefined。所以你在let a定義前console.log(a)的時候會得到Uncaught ReferenceError: Cannot access 'a' before initialization

          為什么要有兩個詞法環(huán)境

          變量環(huán)境組件(VariableEnvironment) 是用來登記var function變量聲明,詞法環(huán)境組件(LexicalEnvironment)是用來登記let const class等變量聲明。

          在ES6之前都沒有塊級作用域,ES6之后我們可以用let const來聲明塊級作用域,有這兩個詞法環(huán)境是為了實現(xiàn)塊級作用域的同時不影響var變量聲明和函數(shù)聲明,具體如下:

          1. 首先在一個正在運行的執(zhí)行上下文內(nèi),詞法環(huán)境由LexicalEnvironmentVariableEnvironment構成,用來登記所有的變量聲明。
          2. 當執(zhí)行到塊級代碼時候,會先LexicalEnvironment記錄下來,記錄為oldEnv
          3. 創(chuàng)建一個新的LexicalEnvironment(outer指向oldEnv),記錄為newEnv,并將newEnv設置為正在執(zhí)行上下文的LexicalEnvironment
          4. 塊級代碼內(nèi)的let const會登記在newEnv里面,但是var聲明和函數(shù)聲明還是登記在原來的VariableEnvironment里。
          5. 塊級代碼執(zhí)行結束后,將oldEnv還原為正在執(zhí)行上下文的LexicalEnvironment
          function foo(){
              var a = 1
              let b = 2
              {
                let b = 3
                var c = 4
                let d = 5
                console.log(a)
                console.log(b)
              }
              console.log(b) 
              console.log(c)
              console.log(d)
          }   
          foo()

          從圖中可以看出,當進入函數(shù)的作用域塊時,作用域塊中通過let聲明的變量,會被存放在詞法環(huán)境的一個單獨的區(qū)域中,這個區(qū)域中的變量并不影響作用域塊外面的變量,比如在作用域外面聲明了變量b,在該作用域塊內(nèi)部也聲明了變量b,當執(zhí)行到作用域內(nèi)部時,它們都是獨立的存在。

          其實,在詞法環(huán)境內(nèi)部,維護了一個小型棧結構,棧底是函數(shù)最外層的變量,進入一個作用域塊后,就會把該作用域塊內(nèi)部的變量壓到棧頂;當作用域執(zhí)行完成之后,該作用域的信息就會從棧頂彈出,這就是詞法環(huán)境的結構。需要注意下,我這里所講的變量是指通過let或者const聲明的變量。

          再接下來,當執(zhí)行到作用域塊中的console.log(a)這行代碼時,就需要在詞法環(huán)境和變量環(huán)境中查找變量a的值了,具體查找方式是:沿著詞法環(huán)境的棧頂向下查詢,如果在詞法環(huán)境中的某個塊中查找到了,就直接返回給JavaScript引擎,如果沒有查找到,那么繼續(xù)在變量環(huán)境中查找。

          執(zhí)行棧 Execution Context Stack

          每個函數(shù)都會有自己的執(zhí)行上下文,多個執(zhí)行上下文就會以棧(調(diào)用棧)的方式來管理。

          function a ({
            console.log('In fn a')
            function b ({
              console.log('In fn b')
              function c ({
                console.log('In fn c')
              }
              c()
            }
            b()
          }
          a()

          可以用這個工具試一下,更直觀的觀察進棧和出棧javascript visualizer 工具。

          看這個圖就可以看出作用域鏈了吧,很直觀。作用域鏈就是在執(zhí)行上下文創(chuàng)建階段確定的。有了執(zhí)行的環(huán)境,才能確定它應該和誰構成作用域鏈。

          4V8垃圾回收

          內(nèi)存分配

          棧是臨時存儲空間,主要存儲局部變量和函數(shù)調(diào)用,內(nèi)小且存儲連續(xù),操作起來簡單方便,一般由系統(tǒng)自動分配自動回收,所以文章內(nèi)所說的垃圾回收,都是基于堆內(nèi)存。

          基本類型數(shù)據(jù)(Number, Boolean, String, Null, Undefined, Symbol, BigInt)保存在在棧內(nèi)存中。引用類型數(shù)據(jù)保存在堆內(nèi)存中,引用數(shù)據(jù)類型的變量是一個指向堆內(nèi)存中實際對象的引用,存在棧中。

          為什么基本數(shù)據(jù)類型存儲在棧中,引用數(shù)據(jù)類型存儲在堆中?

          JavaScript引擎需要用棧來維護程序執(zhí)行期間的上下文的狀態(tài),如果棧空間大了的話,所有數(shù)據(jù)都存放在棧空間里面,會影響到上下文切換的效率,進而影響整個程序的執(zhí)行效率。

          這里用來存儲對象和動態(tài)數(shù)據(jù),這是內(nèi)存中最大的區(qū)域,并且是GC(Garbage collection 垃圾回收)工作的地方。不過,并不是所有的堆內(nèi)存都可以進行GC,只有新生代和老生代被GC管理。堆可以進一步細分為下面這樣:

          • 新生代空間:是最新產(chǎn)生的數(shù)據(jù)存活的地方,這些數(shù)據(jù)往往都是短暫的。這個空間被一分為二,然后被Scavenger(Minor GC)所管理。稍后會介紹。可以通過V8標志如 --max_semi_space_size 或 --min_semi_space_size 來控制新生代空間大小
          • 老生代空間:是從新生代空間經(jīng)過至少兩輪Minor GC仍然存活下來的數(shù)據(jù),該空間被Major GC(Mark-Sweep & Mark-Compact)管理,稍后會介紹。可以通過 --initial_old_space_size 或 --max_old_space_size控制空間大小。

          Old pointer space:存活下來的包含指向其他對象指針的對象

          Old data space:存活下來的只包含數(shù)據(jù)的對象。

          • 大對象空間:這是比空間大小還要大的對象,大對象不會被gc處理。
          • 代碼空間:這里是JIT所編譯的代碼。這是除了在大對象空間中分配代碼并執(zhí)行之外的唯一可執(zhí)行的空間。
          • map空間:存放 Cell 和 Map,每個區(qū)域都是存放相同大小的元素,結構簡單。

          代際假說

          代際假說有以下兩個特點:

          • 第一個是大部分對象在內(nèi)存中存在的時間很短,簡單來說,就是很多對象一經(jīng)分配內(nèi)存,很快就變得不可訪問;
          • 第二個是不死的對象,會活得更久。

          在 V8 中會把堆分為新生代老生代兩個區(qū)域,新生代中存放的是生存時間短的對象,老生代中存放的生存時間久的對象。

          新生區(qū)通常只支持 1~8M 的容量,而老生區(qū)支持的容量就大很多了。對于這兩塊區(qū)域,V8 分別使用兩個不同的垃圾回收器,以便更高效地實施垃圾回收。

          • 副垃圾回收器,主要負責新生代的垃圾回收。
          • 主垃圾回收器,主要負責老生代的垃圾回收。

          新生代中用Scavenge算法來處理。所謂 Scavenge 算法,是把新生代空間對半劃分為兩個區(qū)域,一半是對象區(qū)域,一半是空閑區(qū)域。

          新生代回收

          新加入的對象都會存放到對象區(qū)域,當對象區(qū)域快被寫滿時,就需要執(zhí)行一次垃圾清理操作。

          1. 先標記需要回收的對象,然后把對象區(qū)激活對象復制到空閑區(qū),并排序;
          2. 完成復制后,對象區(qū)域與空閑區(qū)域進行角色翻轉(zhuǎn),也就是原來的對象區(qū)域變成空閑區(qū)域,原來的空閑區(qū)域變成了對象區(qū)域。

          由于新生代中采用的 Scavenge 算法,所以每次執(zhí)行清理操作時,都需要將存活的對象從對象區(qū)域復制到空閑區(qū)域。但復制操作需要時間成本,如果新生區(qū)空間設置得太大了,那么每次清理的時間就會過久,所以為了執(zhí)行效率,一般新生區(qū)的空間會被設置得比較小。

          也正是因為新生區(qū)的空間不大,所以很容易被存活的對象裝滿整個區(qū)域。為了解決這個問題,JavaScript 引擎采用了對象晉升策略,也就是經(jīng)過兩次垃圾回收依然還存活的對象,會被移動到老生區(qū)中。

          老生代回收

          Mark-Sweep

          Mark-Sweep處理時分為兩階段,標記階段和清理階段,看起來與Scavenge類似,不同的是,Scavenge算法是復制活動對象,而由于在老生代中活動對象占大多數(shù),所以Mark-Sweep在標記了活動對象和非活動對象之后,直接把非活動對象清除。

          • 標記階段:對老生代進行第一次掃描,標記活動對象
          • 清理階段:對老生代進行第二次掃描,清除未被標記的對象,即清理非活動對象

          Mark-Compact

          由于Mark-Sweep完成之后,老生代的內(nèi)存中產(chǎn)生了很多內(nèi)存碎片,若不清理這些內(nèi)存碎片,如果出現(xiàn)需要分配一個大對象的時候,這時所有的碎片空間都完全無法完成分配,就會提前觸發(fā)垃圾回收,而這次回收其實不是必要的。

          為了解決內(nèi)存碎片問題,Mark-Compact被提出,它是在是在 Mark-Sweep的基礎上演進而來的,相比Mark-Sweep,Mark-Compact添加了活動對象整理階段,將所有的活動對象往一端移動,移動完成后,直接清理掉邊界外的內(nèi)存。

          全停頓 Stop-The-World

          垃圾回收如果耗費時間,那么主線程的JS操作就要停下來等待垃圾回收完成繼續(xù)執(zhí)行,我們把這種行為叫做全停頓(Stop-The-World)。

          增量標記

          為了降低老生代的垃圾回收而造成的卡頓,V8 將標記過程分為一個個的子標記過程,同時讓垃圾回收標記和 JavaScript 應用邏輯交替進行,直到標記階段完成,我們把這個算法稱為增量標記(Incremental Marking)算法。如下圖所示:

          惰性清理

          增量標記只是對活動對象和非活動對象進行標記,惰性清理用來真正的清理釋放內(nèi)存。當增量標記完成后,假如當前的可用內(nèi)存足以讓我們快速的執(zhí)行代碼,其實我們是沒必要立即清理內(nèi)存的,可以將清理的過程延遲一下,讓JavaScript邏輯代碼先執(zhí)行,也無需一次性清理完所有非活動對象內(nèi)存,垃圾回收器會按需逐一進行清理,直到所有的頁都清理完畢。

          并發(fā)回收

          并發(fā)式GC允許在在垃圾回收的同時不需要將主線程掛起,兩者可以同時進行,只有在個別時候需要短暫停下來讓垃圾回收器做一些特殊的操作。但是這種方式也要面對增量回收的問題,就是在垃圾回收過程中,由于JavaScript代碼在執(zhí)行,堆中的對象的引用關系隨時可能會變化,所以也要進行寫屏障操作。

          并行回收

          并行式GC允許主線程和輔助線程同時執(zhí)行同樣的GC工作,這樣可以讓輔助線程來分擔主線程的GC工作,使得垃圾回收所耗費的時間等于總時間除以參與的線程數(shù)量(加上一些同步開銷)。

          5站在巨人的肩膀上

          在這里對前輩大佬表示敬意,查找了很多資料,如有遺漏,還請見諒。文中如果有誤,還望及時指出,感謝!

          • 瀏覽器工作原理與實踐
          • 讀李老課程引發(fā)的思考之JS執(zhí)行機制-|超級 · 奧義|
          • 瀏覽器原理學習筆記-瀏覽器中 js 執(zhí)行機制(上)
          • js引擎的執(zhí)行過程
          • 初步理解 JavaScript 底層原理
          • JavaScript語言在引擎級別的執(zhí)行過程
          • 前端基礎 | js執(zhí)行過程你了解多少?
          • 【編譯】代碼是如何運行的之JavaScript執(zhí)行過程
          • V8是如何執(zhí)行JavaScript代碼的?
          • 視野前端(二)V8引擎是如何工作的
          • 深入了解JavaScript執(zhí)行過程(JS系列之一)
          • 深入淺出講解V8引擎如何執(zhí)行JavaScript代碼
          • 瀏覽器是如何工作的:Chrome V8讓你更懂JavaScript
          • 如何理解js的執(zhí)行上下文與執(zhí)行棧
          • js執(zhí)行可視化
          • 【譯】理解 Javascript 執(zhí)行上下文和執(zhí)行棧
          • 【譯】理解 Javascript 執(zhí)行上下文和執(zhí)行棧
          • JS作用域鏈的詳解
          • 瀏覽器的垃圾回收詳解(以谷歌瀏覽器的V8為例)
          • 深入理解谷歌最強V8垃圾回收機制
          • 「譯」Orinoco: V8的垃圾回收器
          • V8內(nèi)存管理及垃圾回收機制
          • JS Memory Leak And V8 Garbage Collection

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設計模式 重溫系列(9篇全)
          4. 正則 / 框架 / 算法等 重溫系列(16篇全)
          5. Webpack4 入門(上)|| Webpack4 入門(下)
          6. MobX 入門(上) ||  MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復“加群”與大佬們一起交流學習~

          點擊“閱讀原文”查看 120+ 篇原創(chuàng)文章

          瀏覽 59
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  の夫婦交換中中文字幕 | 老牛AV国产性久久 | 91玉足脚交白嫩脚丫 | 中文无码一区二区三区四区 | 欧美日韩国产在线播放 |