日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

浏览器是如何工作的:Chrome V8让你更懂JavaScript

發布時間:2023/12/14 javascript 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浏览器是如何工作的:Chrome V8让你更懂JavaScript 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

關注?前端開發博客,回復“加群”

加入我們一起學習,天天進步

文章來源:https://segmentfault.com/a/1190000037435824


??V8 是由 Google 開發的開源 JavaScript 引擎,也被稱為虛擬機,模擬實際計算機各種功能來實現代碼的編譯和執行

上圖清晰版

記得那年花下,深夜,初識謝娘時

為什么需要 JavaScript 引擎

我們寫的 JavaScript 代碼直接交給瀏覽器或者 Node 執行時,底層的 CPU 是不認識的,也沒法執行。CPU 只認識自己的指令集,指令集對應的是匯編代碼。寫匯編代碼是一件很痛苦的事情。并且不同類型的 CPU 的指令集是不一樣的,那就意味著需要給每一種 CPU 重寫匯編代碼
??JavaScirpt 引擎可以將 JS 代碼編譯為不同 CPU(Intel, ARM 以及 MIPS 等)對應的匯編代碼,這樣我們就不需要去翻閱每個 CPU 的指令集手冊來編寫匯編代碼了。當然,JavaScript 引擎的工作也不只是編譯代碼,它還要負責執行代碼、分配內存以及垃圾回收

  • #?將一個寄存器中的數據移動到另外一個寄存器中
  • 1000100111011000??#機器指令
  • mov?ax,bx?????????#匯編指令
  • 資料拓展:匯編語言入門教程【阮一峰】

    熱門 JavaScript 引擎

    • V8 (Google),用 C++編寫,開放源代碼,由 Google 丹麥開發,是 Google Chrome 的一部分,也用于 Node.js。

    • JavaScriptCore (Apple),開放源代碼,用于 webkit 型瀏覽器,如 Safari ,2008 年實現了編譯器和字節碼解釋器,升級為了 SquirrelFish。蘋果內部代號為“Nitro”的 JavaScript 引擎也是基于 JavaScriptCore 引擎的。

    • Rhino,由 Mozilla 基金會管理,開放源代碼,完全以 Java 編寫,用于 HTMLUnit

    • SpiderMonkey (Mozilla),第一款 JavaScript 引擎,早期用于 Netscape Navigator,現時用于 Mozilla Firefox。

    • Chakra (JScript 引擎),用于 Internet Explorer。

    • Chakra (JavaScript 引擎),用于 Microsoft Edge。

    • KJS,KDE 的 ECMAScript/JavaScript 引擎,最初由哈里·波頓開發,用于 KDE 項目的 Konqueror 網頁瀏覽器中。

    • JerryScript — 三星推出的適用于嵌入式設備的小型 JavaScript 引擎。

    • 其他:Nashorn、QuickJS 、 Hermes

    V8

    Google V8 引擎是用 C ++編寫的開源高性能 JavaScript 和 WebAssembly 引擎,它已被用于 Chrome 和 Node.js 等。可以運行在 Windows 7+,macOS 10.12+和使用 x64,IA-32,ARM 或 MIPS 處理器的 Linux 系統上。V8 最早被開發用以嵌入到 Google 的開源瀏覽器 Chrome 中,第一個版本隨著第一版Chrome于 2008 年 9 月 2 日發布。但是 V8 是一個可以獨立運行的模塊,完全可以嵌入到任何 C ++應用程序中。著名的 Node.js( 一個異步的服務器框架,可以在服務端使用 JavaScript 寫出高效的網絡服務器 ) 就是基于 V8 引擎的,Couchbase, MongoDB 也使用了 V8 引擎。

    和其他 JavaScript 引擎一樣,V8 會編譯 / 執行 JavaScript 代碼,管理內存,負責垃圾回收,與宿主語言的交互等。通過暴露宿主對象 ( 變量,函數等 ) 到 JavaScript,JavaScript 可以訪問宿主環境中的對象,并在腳本中完成對宿主對象的操作

    how-v8-works

    資料拓展:v8 logo | V8 (JavaScript engine) | 《V8、JavaScript+的現在與未來》 | 幾張圖讓你看懂 WebAssembly

    V8一詞最早見于“V-8 engine”,即V8發動機,一般使用在中高端車輛上。8個氣缸分成兩組,每組4個,成V型排列。是高層次汽車運動中最常見的發動機結構,尤其在美國,IRL,ChampCar和NASCAR都要求使用V8發動機。

    與君初相識,猶如故人歸

    什么是 D8

    d8 是一個非常有用的調試工具,你可以把它看成是 debug for V8 的縮寫。我們可以使用 d8 來查看 V8 在執行 JavaScript 過程中的各種中間數據,比如作用域、AST、字節碼、優化的二進制代碼、垃圾回收的狀態,還可以使用 d8 提供的私有 API 查看一些內部信息

    V8源碼編譯出來的可執行程序名為d8。d8作為V8引擎在命令行中可以使用的交互shell存在。Google官方已經不記得d8這個名字的由來,但是做為"delveloper shell"的縮寫,用首字母d和8結合,恰到好處。
    還有一種說法是d8最初叫developer shell,因為d后面有8個字符,因此簡寫為d8,類似于i18n(internationalization)這樣的簡寫。
    參考:Using d8

    安裝 D8

    • 方法一:自行下載編譯

      • v8 google 下載及編譯使用

      • 官方文檔:Using d8

    • 方法二:使用編譯好的 d8 工具

      • mac 平臺

      • linux32 平臺

      • linux64 平臺

      • win32 平臺

      • win64 平臺

        // 解壓文件,點擊d8打開(mac安全策略限制的話,按住control,再點擊,彈出菜單中選擇打開) V8 version 8.4.109 d8> 1 + 2 3 d8> 2 + '4' "24" d8> console.log(23) 23 undefined d8> var a = 1 undefined d8> a + 2 3 d8> this [object global] d8>

    本文后續用于 demo 演示時的文件目錄結構:

  • V8:
  • #?d8可執行文件
  • d8
  • icudtl.dat
  • libc++.dylib
  • libchrome_zlib.dylib
  • libicui18n.dylib
  • libicuuc.dylib
  • libv8.dylib
  • libv8_debug_helper.dylib
  • libv8_for_testing.dylib
  • libv8_libbase.dylib
  • libv8_libplatform.dylib
  • obj
  • snapshot_blob.bin
  • v8_build_config.json
  • #?新建的js示例文件
  • test.js
    • 方法三:mac

    • #?如果已有HomeBrew,忽略第一條命令
    • ruby?-e?"$(curl?-fsSL?https://raw.githubusercontent.com/Homebrew/install/master/install)"
    • brew?install?v8
    • 方法四:使用 node 代替,比如可以用node --print-bytecode ./test.js,打印出 Ignition(解釋器)生成的 Bytecode(字節碼)。

    都有哪些 d8 命令可供使用?

    • 查看 d8 命令

  • #?如果不想使用./d8這種方式進行調試,可將d8加入環境變量,之后就可以直接`d8?--help`
  • ./d8?--help
    • 過濾特定的命令

  • #?如果是?Windows?系統,可能缺少?grep?程序,請自行下載安裝并添加環境變量
  • ./d8?--help?|grep?print
  • 如:

    • print-bytecode 查看生成的字節碼

    • print-opt-code 查看優化后的代碼

    • print-ast 查看中間生成的 AST

    • print-scopes 查看中間生成的作用域

    • trace-gc 查看這段代碼的內存回收狀態

    • trace-opt 查看哪些代碼被優化了

    • trace-deopt 查看哪些代碼被反優化了

    • turbofan-stats 打印優化編譯器的一些統計數據

    使用 d8 進行調試

  • //?test.js
  • function?sum(a)?{
  • var?b?=?6;
  • return?a?+?6;
  • }
  • console.log(sum(3));
  • # d8 后面跟上文件名和要執行的命令,如執行下面這行命令,就會打印出 test.js 文件所生成的字節碼。
  • ./d8?./test.js?--print-bytecode
  • #?執行以下命令,輸出9
  • ./d8?./test.js
  • 內部方法

    你還可以使用 V8 所提供的一些內部方法,只需要在啟動 V8 時傳入 --allow-natives-syntax 命令,你就可以在 test.js 中使用諸如HasFastProperties(檢查一個對象是否擁有快屬性)的內部方法(索引屬性、常規屬性、快屬性等下文會介紹)。

  • function?Foo(property_num,?element_num)?{
  • //添加可索引屬性
  • for?(let?i?=?0;?i?<?element_num;?i++)?{
  • this[i]?=?`element${i}`;
  • }
  • //添加常規屬性
  • for?(let?i?=?0;?i?<?property_num;?i++)?{
  • let?ppt?=?`property${i}`;
  • this[ppt]?=?ppt;
  • }
  • }
  • var?bar?=?new?Foo(10,?10);
  • //?檢查一個對象是否擁有快屬性
  • console.log(%HasFastProperties(bar));
  • delete?bar.property2;
  • console.log(%HasFastProperties(bar));
  • ./d8?--allow-natives-syntax?./test.js
  • #?依次打印:true false
  • 心似雙絲網,中有千千結

    V8 引擎的內部結構

    V8 是一個非常復雜的項目,有超過 100 萬行 C++代碼。它由許多子模塊構成,其中這 4 個模塊是最重要的:

    • Parser:負責將 JavaScript 源碼轉換為 Abstract Syntax Tree (AST)

      確切的說,在“Parser”將 JavaScript 源碼轉換為 AST前,還有一個叫”Scanner“的過程,具體流程如下:

    • Ignition:interpreter,即解釋器,負責將 AST 轉換為 Bytecode,解釋執行 Bytecode;同時收集 TurboFan 優化編譯所需的信息,比如函數參數的類型;解釋器執行時主要有四個模塊,內存中的字節碼、寄存器、棧、堆。

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

    • TurboFan:compiler,即編譯器,利用 Ignition 所收集的類型信息,將 Bytecode 轉換為優化的匯編代碼;

    • Orinoco:garbage collector,垃圾回收模塊,負責將程序不再需要的內存空間回收。

    其中,Parser,Ignition 以及 TurboFan 可以將 JS 源碼編譯為匯編代碼,其流程圖如下:

    ??簡單地說,Parser 將 JS 源碼轉換為 AST,然后 Ignition 將 AST 轉換為 Bytecode,最后 TurboFan 將 Bytecode 轉換為經過優化的 Machine Code(實際上是匯編代碼)

    • 如果函數沒有被調用,則 V8 不會去編譯它。

    • 如果函數只被調用 1 次,則 Ignition 將其編譯 Bytecode 就直接解釋執行了。TurboFan 不會進行優化編譯,因為它需要 Ignition 收集函數執行時的類型信息。這就要求函數至少需要執行 1 次,TurboFan 才有可能進行優化編譯。

    • 如果函數被調用多次,則它有可能會被識別為熱點函數,且 Ignition 收集的類型信息證明可以進行優化編譯的話,這時 TurboFan 則會將 Bytecode 編譯為 Optimized Machine Code(已優化的機器碼),以提高代碼的執行性能。

    圖片中的紅色虛線是逆向的,也就是說Optimized Machine Code 會被還原為 Bytecode,這個過程叫做 Deoptimization。這是因為 Ignition 收集的信息可能是錯誤的,比如 add 函數的參數之前是整數,后來又變成了字符串。生成的 Optimized Machine Code 已經假定 add 函數的參數是整數,那當然是錯誤的,于是需要進行 Deoptimization。

  • function?add(x,?y)?{
  • return?x?+?y;
  • }
  • add(3,?5);
  • add('3',?'5');
  • 在運行 C、C++以及 Java 等程序之前,需要進行編譯,不能直接執行源碼;但對于 JavaScript 來說,我們可以直接執行源碼(比如:node test.js),它是在運行的時候先編譯再執行,這種方式被稱為即時編譯(Just-in-time compilation),簡稱為 JIT。因此,V8 也屬于 JIT 編譯器

    資料拓展參考:V8 引擎是如何工作的?

    V8 是怎么執行一段 JavaScript 代碼的

    • V8 出現之前,所有的 JavaScript 虛擬機所采用的都是解釋執行的方式,這是 JavaScript 執行速度過慢的一個主要原因。而 V8 率先引入了即時編譯(JIT)雙輪驅動的設計(混合使用編譯器和解釋器的技術),這是一種權衡策略,混合編譯執行和解釋執行這兩種手段,給 JavaScript 的執行速度帶來了極大的提升。V8 出現之后,各大廠商也都在自己的 JavaScript 虛擬機中引入了 JIT 機制,所以目前市面上 JavaScript 虛擬機都有著類似的架構。另外,V8 也是早于其他虛擬機引入了惰性編譯、內聯緩存、隱藏類等機制,進一步優化了 JavaScript 代碼的編譯執行效率

    • V8 執行一段 JavaScript 的流程圖:

      資料拓展:V8 是如何執行一段 JavaScript 代碼的?

    • V8 本質上是一個虛擬機,因為計算機只能識別二進制指令,所以要讓計算機執行一段高級語言通常有兩種手段:

      • 第一種是將高級代碼轉換為二進制代碼,再讓計算機去執行;

      • 另外一種方式是在計算機安裝一個解釋器,并由解釋器來解釋執行。

    • 解釋執行和編譯執行都有各自的優缺點,解釋執行啟動速度快,但是執行時速度慢,而編譯執行啟動速度慢,但是執行速度快。為了充分地利用解釋執行和編譯執行的優點,規避其缺點,V8 采用了一種權衡策略,在啟動過程中采用了解釋執行的策略,但是如果某段代碼的執行頻率超過一個值,那么 V8 就會采用優化編譯器將其編譯成執行效率更加高效的機器代碼

    • 總結:

      V8 執行一段 JavaScript 代碼所經歷的主要流程包括:

      • 初始化基礎環境;

      • 解析源碼生成 AST 和作用域;

      • 依據 AST 和作用域生成字節碼;

      • 解釋執行字節碼;

      • 監聽熱點代碼;

      • 優化熱點代碼為二進制的機器代碼;

      • 反優化生成的二進制機器代碼。

    一等公民與閉包

    一等公民的定義

    • 在編程語言中,一等公民可以作為函數參數,可以作為函數返回值,也可以賦值給變量。

    • 如果某個編程語言的函數,可以和這個語言的數據類型做一樣的事情,我們就把這個語言中的函數稱為一等公民。例如,字符串在幾乎所有編程語言中都是一等公民,字符串可以做為函數參數,字符串可以作為函數返回值,字符串也可以賦值給變量。對于各種編程語言來說,函數就不一定是一等公民了,比如 Java 8 之前的版本。

    • 對于 JavaScript 來說,函數可以賦值給變量,也可以作為函數參數,還可以作為函數返回值,因此 JavaScript 中函數是一等公民

    動態作用域與靜態作用域

    • 如果一門語言的作用域是靜態作用域,那么符號之間的引用關系能夠根據程序代碼在編譯時就確定清楚,在運行時不會變。某個函數是在哪聲明的,就具有它所在位置的作用域。它能夠訪問哪些變量,那么就跟這些變量綁定了,在運行時就一直能訪問這些變量。即靜態作用域可以由程序代碼決定,在編譯時就能完全確定。大多數語言都是靜態作用域的。

    • 動態作用域(Dynamic Scope)。也就是說,變量引用跟變量聲明不是在編譯時就綁定死了的。在運行時,它是在運行環境中動態地找一個相同名稱的變量。在 macOS 或 Linux 中用的 bash 腳本語言,就是動態作用域的。

    閉包的三個基礎特性

    • JavaScript 語言允許在函數內部定義新的函數

    • 可以在內部函數中訪問父函數中定義的變量

    • 因為 JavaScript 中的函數是一等公民,所以函數可以作為另外一個函數的返回值

  • //?閉包(靜態作用域,一等公民,調用棧的矛盾體)
  • function?foo()?{
  • var?d?=?20;
  • return?function?inner(a,?b)?{
  • const?c?=?a?+?b?+?d;
  • return?c;
  • };
  • }
  • const?f?=?foo();
  • 關于閉包,可參考我以前的一篇文章,在此不再贅述,在此主要談下閉包給 Chrome V8 帶來的問題及其解決策略。

    惰性解析

    所謂惰性解析是指解析器在解析的過程中,如果遇到函數聲明,那么會跳過函數內部的代碼,并不會為其生成 AST 和字節碼,而僅僅生成頂層代碼的 AST 和字節碼。

    • 在編譯 JavaScript 代碼的過程中,V8 并不會一次性將所有的 JavaScript 解析為中間代碼,這主要是基于以下兩點:

      • 首先,如果一次解析和編譯所有的 JavaScript 代碼,過多的代碼會增加編譯時間,這會嚴重影響到首次執行 JavaScript 代碼的速度,讓用戶感覺到卡頓。因為有時候一個頁面的 JavaScript 代碼很大,如果要將所有的代碼一次性解析編譯完成,那么會大大增加用戶的等待時間;

      • 其次,解析完成的字節碼和編譯之后的機器代碼都會存放在內存中,如果一次性解析和編譯所有 JavaScript 代碼,那么這些中間代碼和機器代碼將會一直占用內存

    • 基于以上的原因,所有主流的 JavaScript 虛擬機都實現了惰性解析。

    • 閉包給惰性解析帶來的問題:上文的 d 不能隨著 foo 函數的執行上下文被銷毀掉。

    預解析器

    V8 引入預解析器,比如當解析頂層代碼的時候,遇到了一個函數,那么預解析器并不會直接跳過該函數,而是對該函數做一次快速的預解析。

    • 判斷當前函數是不是存在一些語法上的錯誤,發現了語法錯誤,那么就會向 V8 拋出語法錯誤;

    • 檢查函數內部是否引用了外部變量,如果引用了外部的變量,預解析器會將棧中的變量復制到堆中,在下次執行到該函數的時候,直接使用堆中的引用,這樣就解決了閉包所帶來的問題

    V8 內部是如何存儲對象的:快屬性和慢屬性

    下面的代碼會輸出什么:

  • //?test.js
  • function?Foo()?{
  • this[200]?=?'test-200';
  • this[1]?=?'test-1';
  • this[100]?=?'test-100';
  • this['B']?=?'bar-B';
  • this[50]?=?'test-50';
  • this[9]?=?'test-9';
  • this[8]?=?'test-8';
  • this[3]?=?'test-3';
  • this[5]?=?'test-5';
  • this['D']?=?'bar-D';
  • this['C']?=?'bar-C';
  • }
  • var?bar?=?new?Foo();
  • for?(key?in?bar)?{
  • console.log(`index:${key}??value:${bar[key]}`);
  • }
  • //輸出:
  • //?index:1??value:test-1
  • //?index:3??value:test-3
  • //?index:5??value:test-5
  • //?index:8??value:test-8
  • //?index:9??value:test-9
  • //?index:50??value:test-50
  • //?index:100??value:test-100
  • //?index:200??value:test-200
  • //?index:B??value:bar-B
  • //?index:D??value:bar-D
  • //?index:C??value:bar-C
  • 在ECMAScript 規范中定義了數字屬性應該按照索引值大小升序排列,字符串屬性根據創建時的順序升序排列。在這里我們把對象中的數字屬性稱為排序屬性,在 V8 中被稱為 elements,字符串屬性就被稱為常規屬性,在 V8 中被稱為 properties。在 V8 內部,為了有效地提升存儲和訪問這兩種屬性的性能,分別使用了兩個線性數據結構來分別保存排序屬性和常規屬性。同時 v8 將部分常規屬性直接存儲到對象本身,我們把這稱為對象內屬性 (in-object properties),不過對象內屬性的數量是固定的,默認是 10 個。

  • function?Foo(property_num,?element_num)?{
  • //添加可索引屬性
  • for?(let?i?=?0;?i?<?element_num;?i++)?{
  • this[i]?=?`element${i}`;
  • }
  • //添加常規屬性
  • for?(let?i?=?0;?i?<?property_num;?i++)?{
  • let?ppt?=?`property${i}`;
  • this[ppt]?=?ppt;
  • }
  • }
  • var?bar?=?new?Foo(10,?10);
  • 可以通過 Chrome 開發者工具的 Memory 標簽,捕獲查看當前的內存快照。通過增大第一個參數來查看存儲變化。(Console面板運行以上代碼,打開Memory面板,通過點擊Take heap snapshot記錄內存快照,點擊快照,篩選出Foo進行查看。可參考使用 chrome-devtools Memory 面板了解Memory面板。)

    我們將保存在線性數據結構中的屬性稱之為“快屬性”,因為線性數據結構中只需要通過索引即可以訪問到屬性,雖然訪問線性結構的速度快,但是如果從線性結構中添加或者刪除大量的屬性時,則執行效率會非常低,這主要因為會產生大量時間和內存開銷。因此,如果一個對象的屬性過多時,V8 就會采取另外一種存儲策略,那就是“慢屬性”策略,但慢屬性的對象內部會有獨立的非線性數據結構 (字典) 作為屬性存儲容器。所有的屬性元信息不再是線性存儲的,而是直接保存在屬性字典中。

    v8 屬性存儲:

    總結:

    因為 JavaScript 中的對象是由一組組屬性和值組成的,所以最簡單的方式是使用一個字典來保存屬性和值,但是由于字典是非線性結構,所以如果使用字典,讀取效率會大大降低。為了提升查找效率,V8 在對象中添加了兩個隱藏屬性,排序屬性和常規屬性,element 屬性指向了 elements 對象,在 elements 對象中,會按照順序存放排序屬性。properties 屬性則指向了 properties 對象,在 properties 對象中,會按照創建時的順序保存常規屬性。

    通過引入這兩個屬性,加速了 V8 查找屬性的速度,為了更加進一步提升查找效率,V8 還實現了內置內屬性的策略,當常規屬性少于一定數量時,V8 就會將這些常規屬性直接寫進對象中,這樣又節省了一個中間步驟。

    但是如果對象中的屬性過多時,或者存在反復添加或者刪除屬性的操作,那么 V8 就會將線性的存儲模式降級為非線性的字典存儲模式,這樣雖然降低了查找速度,但是卻提升了修改對象的屬性的速度

    資料拓展:快屬性和慢屬性:V8 是怎樣提升對象屬性訪問速度的?

    堆空間和棧空間

    棧空間

    • 現代語言都是基于函數的,每個函數在執行過程中,都有自己的生命周期和作用域,當函數執行結束時,其作用域也會被銷毀,因此,我們會使用棧這種數據結構來管理函數的調用過程,我們也把管理函數調用過程的棧結構稱之為調用棧

    • 棧空間主要是用來管理 JavaScript 函數調用的,棧是內存中連續的一塊空間,同時棧結構是“先進后出”的策略。在函數調用過程中,涉及到上下文相關的內容都會存放在棧上,比如原生類型、引用到的對象的地址、函數的執行狀態、this 值等都會存在在棧上。當一個函數執行結束,那么該函數的執行上下文便會被銷毀掉。

    • 棧空間的最大的特點是空間連續,所以在棧中每個元素的地址都是固定的,因此棧空間的查找效率非常高,但是通常在內存中,很難分配到一塊很大的連續空間,因此,V8 對棧空間的大小做了限制,如果函數調用層過深,那么 V8 就有可能拋出棧溢出的錯誤。

    • 棧的優勢和缺點:

    • //?棧溢出
    • function?factorial(n)?{
    • if?(n?===?1)?{
    • return?1;
    • }
    • return?n?*?factorial(n?-?1);
    • }
    • console.log(factorial(50000));
      • 棧的結構非常適合函數調用過程。

      • 在棧上分配資源和銷毀資源的速度非常快,這主要歸結于棧空間是連續的,分配空間和銷毀空間只需要移動下指針就可以了。

      • 雖然操作速度非常快,但是棧也是有缺點的,其中最大的缺點也是它的優點所造成的,那就是棧是連續的,所以要想在內存中分配一塊連續的大空間是非常難的,因此棧空間是有限的

    堆空間

    • 堆空間是一種樹形的存儲結構,用來存儲對象類型的離散的數據,JavaScript 中除了原生類型的數據,其他的都是對象類型,諸如函數、數組,在瀏覽器中還有 window 對象、document 對象等,這些都是存在堆空間的。

    • 宿主在啟動 V8 的過程中,會同時創建堆空間和棧空間,再繼續往下執行,產生的新數據都會存放在這兩個空間中。

    繼承

    繼承就是一個對象可以訪問另外一個對象中的屬性和方法,在 JavaScript 中,我們通過原型和原型鏈的方式來實現了繼承特性

    JavaScript 的每個對象都包含了一個隱藏屬性 __proto__ ,我們就把該隱藏屬性 __proto__ 稱之為該對象的原型 (prototype),__proto__ 指向了內存中的另外一個對象,我們就把 __proto__ 指向的對象稱為該對象的原型對象,那么該對象就可以直接訪問其原型對象的方法或者屬性。

    JavaScript 中的繼承非常簡潔,就是每個對象都有一個原型屬性,該屬性指向了原型對象,查找屬性的時候,JavaScript 虛擬機會沿著原型一層一層向上查找,直至找到正確的屬性。

    隱藏屬性__proto__

  • var?animal?=?{
  • type:?'Default',
  • color:?'Default',
  • getInfo:?function?()?{
  • return?`Type?is:?${this.type},color?is?${this.color}.`;
  • },
  • };
  • var?dog?=?{
  • type:?'Dog',
  • color:?'Black',
  • };
  • 利用__proto__實現繼承:

  • dog.__proto__?=?animal;
  • dog.getInfo();
  • 通常隱藏屬性是不能使用 JavaScript 來直接與之交互的。雖然現代瀏覽器都開了一個口子,讓 JavaScript 可以訪問隱藏屬性 __proto__,但是在實際項目中,我們不應該直接通過 __proto__ 來訪問或者修改該屬性,其主要原因有兩個:

    • 首先,這是隱藏屬性,并不是標準定義的;

    • 其次,使用該屬性會造成嚴重的性能問題。因為 JavaScript 通過隱藏類優化了很多原有的對象結構,所以通過直接修改__proto__會直接破壞現有已經優化的結構,觸發 V8 重構該對象的隱藏類!

    構造函數是怎么創建對象的?

    在 JavaScript 中,使用 new 加上構造函數的這種組合來創建對象和實現對象的繼承。不過使用這種方式隱含的語義過于隱晦。其實是 JavaScript 為了吸引 Java 程序員、在語法層面去蹭 Java 熱點,所以就被硬生生地強制加入了非常不協調的關鍵字 new。

  • function?DogFactory(type,?color)?{
  • this.type?=?type;
  • this.color?=?color;
  • }
  • var?dog?=?new?DogFactory('Dog',?'Black');
  • 其實當 V8 執行上面這段代碼時,V8 在背后悄悄地做了以下幾件事情:

  • var?dog?=?{};
  • dog.__proto__?=?DogFactory.prototype;
  • DogFactory.call(dog,?'Dog',?'Black');
  • 機器碼、字節碼

    V8 為什么要引入字節碼

    • 早期的 V8 為了提升代碼的執行速度,直接將 JavaScript 源代碼編譯成了沒有優化的二進制機器代碼,如果某一段二進制代碼執行頻率過高,那么 V8 會將其標記為熱點代碼,熱點代碼會被優化編譯器優化,優化后的機器代碼執行效率更高。

    • 隨著移動設備的普及,V8 團隊逐漸發現將 JavaScript 源碼直接編譯成二進制代碼存在兩個致命的問題:

      • 時間問題:編譯時間過久,影響代碼啟動速度;

      • 空間問題:緩存編譯后的二進制代碼占用更多的內存。

    • 這兩個問題無疑會阻礙 V8 在移動設備上的普及,于是 V8 團隊大規模重構代碼,引入了中間的字節碼。字節碼的優勢有如下三點:

      • 解決啟動問題:生成字節碼的時間很短;

      • 解決空間問題:字節碼雖然占用的空間比原始的 JavaScript 多,但是相較于機器代碼,字節碼還是小了太多,緩存字節碼會大大降低內存的使用。

      • 代碼架構清晰:采用字節碼,可以簡化程序的復雜度,使得 V8 移植到不同的 CPU 架構平臺更加容易。

    • Bytecode 某種程度上就是匯編語言,只是它沒有對應特定的 CPU,或者說它對應的是虛擬的 CPU。這樣的話,生成 Bytecode 時簡單很多,無需為不同的 CPU 生產不同的代碼。要知道,V8 支持 9 種不同的 CPU,引入一個中間層 Bytecode,可以簡化 V8 的編譯流程,提高可擴展性。

    • 如果我們在不同硬件上去生成 Bytecode,會發現生成代碼的指令是一樣的。

    如何查看字節碼

  • //?test.js
  • function?add(x,?y)?{
  • var?z?=?x?+?y;
  • return?z;
  • }
  • console.log(add(1,?2));
  • 運行./d8 ./test.js --print-bytecode:

  • [generated?bytecode?for?function:?add?(0x01000824fe59?<SharedFunctionInfo?add>)]
  • Parameter?count?3?#三個參數,包括了顯式地傳入的?x?和?y,還有一個隱式地傳入的?this
  • Register?count?1
  • Frame?size?8
  • 0x10008250026?@????0?:?25?02?????????????Ldar?a1?#將a1寄存器中的值加載到累加器中,LoaD?Accumulator?from?Register
  • 0x10008250028?@????2?:?34?03?00??????????Add?a0,?[0]
  • 0x1000825002b?@????5?:?26?fb?????????????Star?r0?#Store?Accumulator?to?Register,把累加器中的值保存到r0寄存器中
  • 0x1000825002d?@????7?:?aa????????????????Return??#結束當前函數的執行,并將控制權傳回給調用方
  • Constant?pool?(size?=?0)
  • Handler?Table?(size?=?0)
  • Source?Position?Table?(size?=?0)
  • 3
  • 常用字節碼指令

    • Ldar:表示將寄存器中的值加載到累加器中,你可以把它理解為 LoaD Accumulator from Register,就是把某個寄存器中的值,加載到累加器中。

    • Star:表示 Store Accumulator Register, 你可以把它理解為 Store Accumulator to Register,就是把累加器中的值保存到某個寄存器中

    • Add:Add a0, [0]是從 a0 寄存器加載值并將其與累加器中的值相加,然后將結果再次放入累加器。

      add a0 后面的[0]稱之為 feedback vector slot,又叫反饋向量槽,它是一個數組,解釋器將解釋執行過程中的一些數據類型的分析信息都保存在這個反饋向量槽中了,目的是為了給 TurboFan 優化編譯器提供優化信息,很多字節碼都會為反饋向量槽提供運行時信息。

    • LdaSmi:將小整數(Smi)加載到累加器寄存器中

    • Return:結束當前函數的執行,并將控制權傳回給調用方。返回的值是累加器中的值。

    bytecode-ignition

    V8 中的字節碼指令集 | 理解 V8 的字節碼「譯」

    隱藏類和內聯緩存

    JavaScript 是一門動態語言,其執行效率要低于靜態語言,V8 為了提升 JavaScript 的執行速度,借鑒了很多靜態語言的特性,比如實現了 JIT 機制,為了提升對象的屬性訪問速度而引入了隱藏類,為了加速運算而引入了內聯緩存

    為什么靜態語言的效率更高?

    靜態語言中,如 C++ 在聲明一個對象之前需要定義該對象的結構,代碼在執行之前需要先被編譯,編譯的時候,每個對象的形狀都是固定的,也就是說,在代碼的執行過程中是無法被改變的。可以直接通過偏移量查詢來查詢對象的屬性值,這也就是靜態語言的執行效率高的一個原因。

    JavaScript 在運行時,對象的屬性是可以被修改的,所以當 V8 使用了一個對象時,比如使用了 obj.x 的時候,它并不知道該對象中是否有 x,也不知道 x 相對于對象的偏移量是多少,也就是說 V8 并不知道該對象的具體的形狀。那么,當在 JavaScript 中要查詢對象 obj 中的 x 屬性時,V8 會按照具體的規則一步一步來查詢,這個過程非常的慢且耗時。

    將靜態的特性引入到 V8

    • V8 采用的一個思路就是將 JavaScript 中的對象靜態化,也就是 V8 在運行 JavaScript 的過程中,會假設 JavaScript 中的對象是靜態的。

    • 具體地講,V8 對每個對象做如下兩點假設:

      • 對象創建好了之后就不會添加新的屬性;

      • 對象創建好了之后也不會刪除屬性。

    • 符合這兩個假設之后,V8 就可以對 JavaScript 中的對象做深度優化了。V8 會為每個對象創建一個隱藏類,對象的隱藏類中記錄了該對象一些基礎的布局信息,包括以下兩點:

      • 對象中所包含的所有的屬性;

      • 每個屬性相對于對象的偏移量。

    • 有了隱藏類之后,那么當 V8 訪問某個對象中的某個屬性時,就會先去隱藏類中查找該屬性相對于它的對象的偏移量,有了偏移量和屬性類型,V8 就可以直接去內存中取出對應的屬性值,而不需要經歷一系列的查找過程,那么這就大大提升了 V8 查找對象的效率。

    • 在 V8 中,把隱藏類又稱為 map,每個對象都有一個 map 屬性,其值指向內存中的隱藏類;

    • map 描述了對象的內存布局,比如對象都包括了哪些屬性,這些數據對應于對象的偏移量是多少。

    通過 d8 查看隱藏類

  • //?test.js
  • let?point1?=?{?x:?100,?y:?200?};
  • let?point2?=?{?x:?200,?y:?300?};
  • let?point3?=?{?x:?100?};
  • %DebugPrint(point1);
  • %DebugPrint(point2);
  • %DebugPrint(point3);
  • ./d8?--allow-natives-syntax?./test.js
  • #?===============
  • DebugPrint:?0x1ea3080c5bc5:?[JS_OBJECT_TYPE]
  • #?V8?為?point1?對象創建的隱藏類
  • -?map:?0x1ea308284ce9?<Map(HOLEY_ELEMENTS)>?[FastProperties]
  • -?prototype:?0x1ea308241395?<Object?map?=?0x1ea3082801c1>
  • -?elements:?0x1ea3080406e9?<FixedArray[0]>?[HOLEY_ELEMENTS]
  • -?properties:?0x1ea3080406e9?<FixedArray[0]>?{
  • #x:?100?(const?data?field?0)
  • #y:?200?(const?data?field?1)
  • }
  • 0x1ea308284ce9:?[Map]
  • -?type:?JS_OBJECT_TYPE
  • -?instance?size:?20
  • -?inobject?properties:?2
  • -?elements?kind:?HOLEY_ELEMENTS
  • -?unused?property?fields:?0
  • -?enum?length:?invalid
  • -?stable_map
  • -?back?pointer:?0x1ea308284cc1?<Map(HOLEY_ELEMENTS)>
  • -?prototype_validity?cell:?0x1ea3081c0451?<Cell?value=?1>
  • -?instance?descriptors?(own)?#2:?0x1ea3080c5bf5?<DescriptorArray[2]>
  • -?prototype:?0x1ea308241395?<Object?map?=?0x1ea3082801c1>
  • -?constructor:?0x1ea3082413b1?<JSFunction?Object?(sfi?=?0x1ea3081c557d)>
  • -?dependent?code:?0x1ea3080401ed?<Other?heap?object?(WEAK_FIXED_ARRAY_TYPE)>
  • -?construction?counter:?0
  • #?===============
  • DebugPrint:?0x1ea3080c5c1d:?[JS_OBJECT_TYPE]
  • #?V8?為?point2?對象創建的隱藏類
  • -?map:?0x1ea308284ce9?<Map(HOLEY_ELEMENTS)>?[FastProperties]
  • -?prototype:?0x1ea308241395?<Object?map?=?0x1ea3082801c1>
  • -?elements:?0x1ea3080406e9?<FixedArray[0]>?[HOLEY_ELEMENTS]
  • -?properties:?0x1ea3080406e9?<FixedArray[0]>?{
  • #x:?200?(const?data?field?0)
  • #y:?300?(const?data?field?1)
  • }
  • 0x1ea308284ce9:?[Map]
  • -?type:?JS_OBJECT_TYPE
  • -?instance?size:?20
  • -?inobject?properties:?2
  • -?elements?kind:?HOLEY_ELEMENTS
  • -?unused?property?fields:?0
  • -?enum?length:?invalid
  • -?stable_map
  • -?back?pointer:?0x1ea308284cc1?<Map(HOLEY_ELEMENTS)>
  • -?prototype_validity?cell:?0x1ea3081c0451?<Cell?value=?1>
  • -?instance?descriptors?(own)?#2:?0x1ea3080c5bf5?<DescriptorArray[2]>
  • -?prototype:?0x1ea308241395?<Object?map?=?0x1ea3082801c1>
  • -?constructor:?0x1ea3082413b1?<JSFunction?Object?(sfi?=?0x1ea3081c557d)>
  • -?dependent?code:?0x1ea3080401ed?<Other?heap?object?(WEAK_FIXED_ARRAY_TYPE)>
  • -?construction?counter:?0
  • #?===============
  • DebugPrint:?0x1ea3080c5c31:?[JS_OBJECT_TYPE]
  • #?V8?為?point3?對象創建的隱藏類
  • -?map:?0x1ea308284d39?<Map(HOLEY_ELEMENTS)>?[FastProperties]
  • -?prototype:?0x1ea308241395?<Object?map?=?0x1ea3082801c1>
  • -?elements:?0x1ea3080406e9?<FixedArray[0]>?[HOLEY_ELEMENTS]
  • -?properties:?0x1ea3080406e9?<FixedArray[0]>?{
  • #x:?100?(const?data?field?0)
  • }
  • 0x1ea308284d39:?[Map]
  • -?type:?JS_OBJECT_TYPE
  • -?instance?size:?16
  • -?inobject?properties:?1
  • -?elements?kind:?HOLEY_ELEMENTS
  • -?unused?property?fields:?0
  • -?enum?length:?invalid
  • -?stable_map
  • -?back?pointer:?0x1ea308284d11?<Map(HOLEY_ELEMENTS)>
  • -?prototype_validity?cell:?0x1ea3081c0451?<Cell?value=?1>
  • -?instance?descriptors?(own)?#1:?0x1ea3080c5c41?<DescriptorArray[1]>
  • -?prototype:?0x1ea308241395?<Object?map?=?0x1ea3082801c1>
  • -?constructor:?0x1ea3082413b1?<JSFunction?Object?(sfi?=?0x1ea3081c557d)>
  • -?dependent?code:?0x1ea3080401ed?<Other?heap?object?(WEAK_FIXED_ARRAY_TYPE)>
  • -?construction?counter:?0
  • 多個對象共用一個隱藏類

    • 在 V8 中,每個對象都有一個 map 屬性,該屬性值指向該對象的隱藏類。不過如果兩個對象的形狀是相同的,V8 就會為其復用同一個隱藏類,這樣有兩個好處:

      • 減少隱藏類的創建次數,也間接加速了代碼的執行速度;

      • 減少了隱藏類的存儲空間。

    • 那么,什么情況下兩個對象的形狀是相同的,要滿足以下兩點:

      • 相同的屬性名稱;

      • 相等的屬性個數。

    重新構建隱藏類

    • 給一個對象添加新的屬性,刪除新的屬性,或者改變某個屬性的數據類型都會改變這個對象的形狀,那么勢必也就會觸發 V8 為改變形狀后的對象重建新的隱藏類。

  • //?test.js
  • let?point?=?{};
  • %DebugPrint(point);
  • point.x?=?100;
  • %DebugPrint(point);
  • point.y?=?200;
  • %DebugPrint(point);
  • #?./d8?--allow-natives-syntax?./test.js
  • DebugPrint:?0x32c7080c5b2d:?[JS_OBJECT_TYPE]
  • -?map:?0x32c7082802d9?<Map(HOLEY_ELEMENTS)>?[FastProperties]
  • ...
  • DebugPrint:?0x32c7080c5b2d:?[JS_OBJECT_TYPE]
  • -?map:?0x32c708284cc1?<Map(HOLEY_ELEMENTS)>?[FastProperties]
  • ...
  • DebugPrint:?0x32c7080c5b2d:?[JS_OBJECT_TYPE]
  • -?map:?0x32c708284ce9?<Map(HOLEY_ELEMENTS)>?[FastProperties]
  • ...
    • 每次給對象添加了一個新屬性之后,該對象的隱藏類的地址都會改變,這也就意味著隱藏類也隨著改變了;如果刪除對象的某個屬性,那么對象的形狀也就隨著發生了改變,這時 V8 也會重建該對象的隱藏類;

    • 最佳實踐

      • 使用字面量初始化對象時,要保證屬性的順序是一致的;

      • 盡量使用字面量一次性初始化完整對象屬性;

      • 盡量避免使用 delete 方法。

    通過內聯緩存來提升函數執行效率

    雖然隱藏類能夠加速查找對象的速度,但是在 V8 查找對象屬性值的過程中,依然有查找對象的隱藏類和根據隱藏類來查找對象屬性值的過程。如果一個函數中利用了對象的屬性,并且這個函數會被多次執行:

  • function?loadX(obj)?{
  • return?obj.x;
  • }
  • var?obj?=?{?x:?1,?y:?3?};
  • var?obj1?=?{?x:?3,?y:?6?};
  • var?obj2?=?{?x:?3,?y:?6,?z:?8?};
  • for?(var?i?=?0;?i?<?100;?i++)?{
  • //?對比時間差異
  • console.time(`---${i}----`)
  • loadX(obj);
  • console.timeEnd(`---${i}----`)
  • loadX(obj1);
  • //?產生多態
  • loadX(obj2);
  • }
  • 通常 V8 獲取 obj.x 的流程

    • 找對象 obj 的隱藏類;

    • 再通過隱藏類查找 x 屬性偏移量;

    • 然后根據偏移量獲取屬性值,在這段代碼中 loadX 函數會被反復執行,那么獲取 obj.x 的流程也需要反復被執行;

    內聯緩存及其原理

    • 函數 loadX 在一個 for 循環里面被重復執行了很多次,因此 V8 會想盡一切辦法來壓縮這個查找過程,以提升對象的查找效率。這個加速函數執行的策略就是內聯緩存 (Inline Cache),簡稱為 IC;

    • IC 的原理:在 V8 執行函數的過程中,會觀察函數中一些調用點 (CallSite) 上的關鍵中間數據,然后將這些數據緩存起來,當下次再次執行該函數的時候,V8 就可以直接利用這些中間數據,節省了再次獲取這些數據的過程,因此 V8 利用 IC,可以有效提升一些重復代碼的執行效率。

    • IC 會為每個函數維護一個反饋向量 (FeedBack Vector),反饋向量記錄了函數在執行過程中的一些關鍵的中間數據。

    • 反饋向量其實就是一個表結構,它由很多項組成的,每一項稱為一個插槽 (Slot),V8 會依次將執行 loadX 函數的中間數據寫入到反饋向量的插槽中。

    • 當 V8 再次調用 loadX 函數時,比如執行到 loadX 函數中的 return obj.x 語句時,它就會在對應的插槽中查找 x 屬性的偏移量,之后 V8 就能直接去內存中獲取 obj.x 的屬性值了。這樣就大大提升了 V8 的執行效率。

    單態、多態和超態

    • 如果一個插槽中只包含 1 個隱藏類,那么我們稱這種狀態為單態 (monomorphic);

    • 如果一個插槽中包含了 2 ~ 4 個隱藏類,那我們稱這種狀態為多態 (polymorphic);

    • 如果一個插槽中超過 4 個隱藏類,那我們稱這種狀態為超態 (magamorphic)。

    • 單態的性能優于多態和超態,所以我們需要稍微避免多態和超態的情況。要避免多態和超態,那么就盡量默認所有的對象屬性是不變的,比如你寫了一個 loadX(obj) 的函數,那么當傳遞參數時,盡量不要使用多個不同形狀的 obj 對象。

    總結:
    ??V8 引入了內聯緩存(IC),IC 會監聽每個函數的執行過程,并在一些關鍵的地方埋下監聽點,這些包括了加載對象屬性 (Load)、給對象屬性賦值 (Store)、還有函數調用 (Call),V8 會將監聽到的數據寫入一個稱為反饋向量 (FeedBack Vector) 的結構中,同時 V8 會為每個執行的函數維護一個反饋向量。有了反饋向量緩存的臨時數據,V8 就可以縮短對象屬性的查找路徑,從而提升執行效率。但是針對函數中的同一段代碼,如果對象的隱藏類是不同的,那么反饋向量也會記錄這些不同的隱藏類,這就出現了多態和超態的情況。我們在實際項目中,要盡量避免出現多態或者超態的情況

    異步編程與消息隊列

    V8 是如何執行回調函數的

    回調函數有兩種類型:同步回調和異步回調,同步回調函數是在執行函數內部被執行的,而異步回調函數是在執行函數外部被執行的。
    ??通用 UI 線程宏觀架構:

    ??UI 線程提供一個消息隊列,并將待執行的事件添加到消息隊列中,然后 UI 線程會不斷循環地從消息隊列中取出事件、執行事件。關于異步回調,這里也有兩種不同的類型,其典型代表是 setTimeout 和 XMLHttpRequest:

    • setTimeout 的執行流程其實是比較簡單的,在 setTimeout 函數內部封裝回調消息,并將回調消息添加進消息隊列,然后主線程從消息隊列中取出回調事件,并執行回調函數。

    • XMLHttpRequest 稍微復雜一點,因為下載過程需要放到單獨的一個線程中去執行,所以執行 XMLHttpRequest.send 的時候,宿主會將實際請求轉發給網絡線程,然后 send 函數退出,主線程繼續執行下面的任務。網絡線程在執行下載的過程中,會將一些中間信息和回調函數封裝成新的消息,并將其添加進消息隊列中,然后主線程從消息隊列中取出回調事件,并執行回調函數。

    宏任務和微任務

    • 調用棧:調用棧是一種數據結構,用來管理在主線程上執行的函數的調用關系。主線程在執行任務的過程中,如果函數的調用層次過深,可能造成棧溢出的錯誤,我們可以使用 setTimeout 來解決棧溢出的問題。setTimeout 的本質是將同步函數調用改成異步函數調用,這里的異步調用是將回調函數封裝成宏任務,并將其添加進消息隊列中,然后主線程再按照一定規則循環地從消息隊列中讀取下一個宏任務。

    • 宏任務:就是指消息隊列中的等待被主線程執行的事件。每個宏任務在執行時,V8 都會重新創建棧,然后隨著宏任務中函數調用,棧也隨之變化,最終,當該宏任務執行結束時,整個棧又會被清空,接著主線程繼續執行下一個宏任務。

    • 微任務:你可以把微任務看成是一個需要異步執行的函數,執行時機是在主函數執行結束之后、當前宏任務結束之前。

    • JavaScript 中之所以要引入微任務,主要是由于主線程執行消息隊列中宏任務的時間顆粒度太粗了,無法勝任一些對精度和實時性要求較高的場景,微任務可以在實時性和效率之間做一個有效的權衡。另外使用微任務,可以改變我們現在的異步編程模型,使得我們可以使用同步形式的代碼來編寫異步調用。

    • 微任務是基于消息隊列、事件循環、UI 主線程還有堆棧而來的,然后基于微任務,又可以延伸出協程、Promise、Generator、await/async 等現代前端經常使用的一些技術。

      微任務技術棧
    • //?不會使瀏覽器卡死
    • function?foo()?{
    • setTimeout(foo,?0);
    • }
    • foo();

    • 微任務:

    • //?瀏覽器console控制臺可使瀏覽器卡死(無法響應鼠標事件等)
    • function?foo()?{
    • return?Promise.resolve().then(foo);
    • }
    • foo();
    • 如果當前的任務中產生了一個微任務,通過 Promise.resolve() 或者 Promise.reject() 都會觸發微任務,觸發的微任務不會在當前的函數中被執行,所以執行微任務時,不會導致棧的無限擴張

    • 和異步調用不同,微任務依然會在當前任務執行結束之前被執行,這也就意味著在當前微任務執行結束之前,消息隊列中的其他任務是不可能被執行的。因此在函數內部觸發的微任務,一定比在函數內部觸發的宏任務要優先執行。

    • 微任務依然是在當前的任務中執行的,所以如果在微任務中循環觸發新的微任務,那么將導致消息隊列中的其他任務沒有機會被執行。

    前端異步編程方案史

    前端異步編程方案史
    • Callback 模式的異步編程模型需要實現大量的回調函數,大量的回調函數會打亂代碼的正常邏輯,使得代碼變得不線性、不易閱讀,這就是我們所說的回調地獄問題

    • Promise 能很好地解決回調地獄的問題,我們可以按照線性的思路來編寫代碼,這個過程是線性的,非常符合人的直覺。

    • 但是這種方式充滿了 Promise 的 then() 方法,如果處理流程比較復雜的話,那么整段代碼將充斥著大量的 then,語義化不明顯,代碼不能很好地表示執行流程。我們想要通過線性的方式來編寫異步代碼,要實現這個理想,最關鍵的是要能實現函數暫停和恢復執行的功能。而生成器就可以實現函數暫停和恢復,我們可以在生成器中使用同步代碼的邏輯來異步代碼 (實現該邏輯的核心是協程)。

    • 但是在生成器之外,我們還需要一個觸發器來驅動生成器的執行。前端的最終方案就是 async/await,async 是一個可以暫停和恢復執行的函數,在 async 函數內部使用 await 來暫停 async 函數的執行,await 等待的是一個 Promise 對象,如果 Promise 的狀態變成 resolve 或者 reject,那么 async 函數會恢復執行。因此,使用 async/await 可以實現以同步的方式編寫異步代碼這一目標。和生成器函數一樣,使用了 async 聲明的函數在執行時,也是一個單獨的協程,我們可以使用 await 來暫停該協程,由于 await 等待的是一個 Promise 對象,我們可以 resolve 來恢復該協程。

    協程是一種比線程更加輕量級的存在。你可以把協程看成是跑在線程上的任務,一個線程上可以存在多個協程,但是在線程上同時只能執行一個協程。比如,當前執行的是 A 協程,要啟動 B 協程,那么 A 協程就需要將主線程的控制權交給 B 協程,這就體現在 A 協程暫停執行,B 協程恢復執行;同樣,也可以從 B 協程中啟動 A 協程。通常,如果從 A 協程啟動 B 協程,我們就把 A 協程稱為 B 協程的父協程。

    正如一個進程可以擁有多個線程一樣,一個線程也可以擁有多個協程。每一時刻,該線程只能執行其中某一個協程。最重要的是,協程不是被操作系統內核所管理,而完全是由程序所控制(也就是在用戶態執行)。這樣帶來的好處就是性能得到了很大的提升,不會像線程切換那樣消耗資源。

    資料拓展:co 函數庫的含義和用法

    垃圾回收

    垃圾數據

    從“GC Roots”對象出發,遍歷 GC Root 中的所有對象,如果通過 GC Roots 沒有遍歷到的對象,則這些對象便是垃圾數據。V8 會有專門的垃圾回收器來回收這些垃圾數據。

    垃圾回收算法

    垃圾回收大致可以分為以下幾個步驟:

    • 第一步,通過 GC Root 標記空間中活動對象和非活動對象。目前 V8 采用的可訪問性(reachability)算法來判斷堆中的對象是否是活動對象。具體地講,這個算法是將一些 GC Root 作為初始存活的對象的集合,從 GC Roots 對象出發,遍歷 GC Root 中的所有對象:

      • 全局的 window 對象(位于每個 iframe 中);

      • 文檔 DOM 樹,由可以通過遍歷文檔到達的所有原生 DOM 節點組成;

      • 存放棧上變量。

      • 通過 GC Root 遍歷到的對象,我們就認為該對象是可訪問的(reachable),那么必須保證這些對象應該在內存中保留,我們也稱可訪問的對象為活動對象

      • 通過 GC Roots 沒有遍歷到的對象,則是不可訪問的(unreachable),那么這些不可訪問的對象就可能被回收,我們稱不可訪問的對象為非活動對象

      • 瀏覽器環境中,GC Root 有很多,通常包括了以下幾種 (但是不止于這幾種):

    • 第二步,回收非活動對象所占據的內存。其實就是在所有的標記完成之后,統一清理內存中所有被標記為可回收的對象。

    • 第三步,做內存整理。一般來說,頻繁回收對象后,內存中就會存在大量不連續空間,我們把這些不連續的內存空間稱為內存碎片。當內存中出現了大量的內存碎片之后,如果需要分配較大的連續內存時,就有可能出現內存不足的情況,所以最后一步需要整理這些內存碎片。但這步其實是可選的,因為有的垃圾回收器不會產生內存碎片(比如副垃圾回收器)

    垃圾回收

    • V8 依據代際假說,將堆內存劃分為新生代和老生代兩個區域,新生代中存放的是生存時間短的對象,老生代中存放生存時間久的對象。代際假說有兩個特點:

      • 第一個是大部分對象都是“朝生夕死”的,也就是說大部分對象在內存中存活的時間很短,比如函數內部聲明的變量,或者塊級作用域中的變量,當函數或者代碼塊執行結束時,作用域中定義的變量就會被銷毀。因此這一類對象一經分配內存,很快就變得不可訪問;

      • 第二個是不死的對象,會活得更久,比如全局的 window、DOM、Web API 等對象。

    • 為了提升垃圾回收的效率,V8 設置了兩個垃圾回收器,主垃圾回收器和副垃圾回收器。

      • 主垃圾回收器主要負責老生代中的垃圾回收。除了新生代中晉升的對象,一些大的對象會直接被分配到老生代里。

      • 老生代中的對象有兩個特點:一個是對象占用空間大;另一個是對象存活時間長。

      • 這種角色翻轉的操作還能讓新生代中的這兩塊區域無限重復使用下去。

      • 副垃圾回收器每次執行清理操作時,都需要將存活的對象從對象區域復制到空閑區域,復制操作需要時間成本,如果新生區空間設置得太大了,那么每次清理的時間就會過久,所以為了執行效率,一般新生區的空間會被設置得比較小

      • 副垃圾回收器還會采用對象晉升策略,也就是移動那些經過兩次垃圾回收依然還存活的對象到老生代中。

      • 主垃圾回收器負責收集老生代中的垃圾數據,副垃圾回收器負責收集新生代中的垃圾數據。

      • 副垃圾回收器采用了 Scavenge 算法,是把新生代空間對半劃分為兩個區域(有些地方也稱作From和To空間),一半是對象區域,一半是空閑區域。新的數據都分配在對象區域,等待對象區域快分配滿的時候,垃圾回收器便執行垃圾回收操作,之后將存活的對象從對象區域拷貝到空閑區域,并將兩個區域互換。

      • 主垃圾回收器回收器主要負責老生代中的垃圾數據的回收操作,會經歷標記、清除和整理過程

    Stop-The-World

    由于 JavaScript 是運行在主線程之上的,因此,一旦執行垃圾回收算法,都需要將正在執行的 JavaScript 腳本暫停下來,待垃圾回收完畢后再恢復腳本執行。我們把這種行為叫做全停頓(Stop-The-World)

    • V8 最開始的垃圾回收器有兩個特點:

      • 第一個是垃圾回收在主線程上執行,

      • 第二個特點是一次執行一個完整的垃圾回收流程。

    • 由于這兩個原因,很容易造成主線程卡頓,所以 V8 采用了很多優化執行效率的方案。

      • 第一個方案是并行回收,在執行一個完整的垃圾回收過程中,垃圾回收器會使用多個輔助線程來并行執行垃圾回收。

      • 第二個方案是增量式垃圾回收,垃圾回收器將標記工作分解為更小的塊,并且穿插在主線程不同的任務之間執行。采用增量垃圾回收時,垃圾回收器沒有必要一次執行完整的垃圾回收過程,每次執行的只是整個垃圾回收過程中的一小部分工作。

      • 第三個方案是并發回收,回收線程在執行 JavaScript 的過程,輔助線程能夠在后臺完成的執行垃圾回收的操作。

        資料參考:深入解讀 V8 引擎的「并發標記」技術

      • 主垃圾回收器就綜合采用了所有的方案(并發標記,增量標記,輔助清理),副垃圾回收器也采用了部分方案。

    似此星辰非昨夜,為誰風露立中宵

    Breaking the JavaScript Speed Limit with V8

    Daniel Clifford 在 Google I/O 2012 上做了一個精彩的演講“Breaking the JavaScript Speed Limit with V8”。在演講中,他深入解釋了 13 個簡單的代碼優化方法,可以讓你的JavaScript代碼在 Chrome V8 引擎編譯/運行時更加快速。在演講中,他介紹了怎么優化,并解釋了原因。下面簡明的列出了13 個 JavaScript 性能提升技巧

  • 在構造函數里初始化所有對象的成員(所以這些實例之后不會改變其隱藏類);

  • 總是以相同的次序初始化對象成員;

  • 盡量使用可以用 31 位有符號整數表示的數;

  • 為數組使用從 0 開始的連續的主鍵;

  • 別預分配大數組(比如大于 64K 個元素)到其最大尺寸,令其尺寸順其自然發展就好;

  • 別刪除數組里的元素,尤其是數字數組;

  • 別加載未初始化或已刪除的元素;

  • 對于固定大小的數組,使用”array literals“初始化(初始化小額定長數組時,用字面量進行初始化);

  • 小數組(小于 64k)在使用之前先預分配正確的尺寸;

  • 請勿在數字數組中存放非數字的值(對象);

  • 盡量使用單一類型(monomorphic)而不是多類型(polymorphic)(如果通過非字面量進行初始化小數組時,切勿觸發類型的重新轉換);

  • 不要使用 try{} catch{}(如果存在 try/catch 代碼快,則將性能敏感的代碼放到一個嵌套的函數中);

  • 在優化后避免在方法中修改隱藏類。

  • 演講資料參考:Performance Tips for JavaScript in V8 | JavaScript V8性能小貼士【譯】 | 內網視頻 | YouTube

    在 V8 引擎里 5 個優化代碼的技巧

  • 對象屬性的順序: 在實例化你的對象屬性的時候一定要使用相同的順序,這樣隱藏類和隨后的優化代碼才能共享;

  • 動態屬性: 在對象實例化之后再添加屬性會強制使得隱藏類變化,并且會減慢為舊隱藏類所優化的代碼的執行。所以,要在對象的構造函數中完成所有屬性的分配;

  • 方法: 重復執行相同的方法會運行的比不同的方法只執行一次要快 (因為內聯緩存);

  • 數組: 避免使用 keys 不是遞增的數字的稀疏數組,這種 key 值不是遞增數字的稀疏數組其實是一個 hash 表。在這種數組中每一個元素的獲取都是昂貴的代價。同時,要避免提前申請大數組。最好的做法是隨著你的需要慢慢的增大數組。最后,不要刪除數組中的元素,因為這會使得 keys 變得稀疏;

  • 標記值 (Tagged values): V8 用 32 位來表示對象和數字。它使用一位來區分它是對象 (flag = 1) 還是一個整型 (flag = 0),也被叫做小整型(SMI),因為它只有 31 位。然后,如果一個數值大于 31 位,V8 將會對其進行 box 操作,然后將其轉換成 double 型,并且創建一個新的對象來裝這個數。所以,為了避免代價很高的 box 操作,盡量使用 31 位的有符號數。

  • 資料參考:How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code | 譯文
    box 操作參考:JavaScript類型:關于類型,有哪些你不知道的細節? | JavaScript 的裝箱和拆箱 | 談談JavaScript中裝箱和拆箱

    JavaScript 啟動性能瓶頸分析與解決方案

    資料參考:JavaScript Start-up Performance | JavaScript 啟動性能瓶頸分析與解決方案

    天長地久有時盡,V8 綿綿無絕期

    • v8官方文檔

    • 圖解 Google V8

    • 瀏覽器工作原理與實踐

    • V8、Chrome、Node.js【知乎專欄】

    • 【譯】 JavaScript 如何工作:對引擎、運行時、調用堆棧的概述

    • 【譯】 JavaScript 如何工作的: 事件循環和異步編程的崛起 + 5 個關于如何使用 async/await 編寫更好的技巧

    資料參考

    • 圖解 Google V8

    • JavaScript深入淺出第4課:V8引擎是如何工作的?【作者:KiwenLau】

    番外篇

    • Chrome插件Console Importer推薦:Easily import JS and CSS resources from Chrome console. (可以在瀏覽器控制臺安裝 loadsh、moment、jQuery 等庫,在控制臺直接驗證、使用這些庫。)

    • 效果圖:

    本文首發于個人博客,歡迎指正和star。
    本文同步發布并于掘金社區。

    最后

    公眾號后臺回復:V8,獲取高清 Chrome V8 思維圖。

    “在看”嗎?在看就點一下吧

    總結

    以上是生活随笔為你收集整理的浏览器是如何工作的:Chrome V8让你更懂JavaScript的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    在线国产中文字幕 | 日日爱夜夜爱 | 天堂v中文 | 日韩欧美在线视频一区二区三区 | 狠狠操狠狠| 超碰在线天天 | 久久婷综合| 天天干亚洲 | 狠狠五月天 | 国产精品免费久久 | 香蕉视频在线免费 | 国产91aaa | 日韩最新在线 | 欧美先锋影音 | 久久精品欧美 | 欧美成天堂网地址 | 成人免费视频播放 | 99久久精品国产系列 | 久久久久久久影视 | 狠狠色免费 | 国产精品va在线观看入 | 在线观看完整版免费 | 毛片一级免费一级 | 狠狠色丁香婷婷综合最新地址 | 国产黄色一级片 | 亚洲免费在线 | 亚洲最新视频在线 | 欧美一级久久 | 在线观看亚洲精品视频 | 国产高清在线视频 | 91精品一区二区三区久久久久久 | 国产激情小视频在线观看 | 丁香五月亚洲综合在线 | 久久视频在线观看免费 | 黄网在线免费观看 | 在线观看精品 | 久久成人午夜视频 | 午夜久久影视 | 在线国产日韩 | 美女精品久久久 | 天堂av官网 | 狠狠的日| 午夜精品一区二区三区在线观看 | 国产精品毛片一区二区三区 | 日韩精品亚洲专区在线观看 | 日韩欧美网址 | 最近中文字幕高清字幕免费mv | 久草在线视频资源 | 香蕉久草在线 | 在线看国产日韩 | 日韩av手机在线观看 | 91视频免费看网站 | 五月婷婷综合在线视频 | 国产一级片免费视频 | 免费在线观看黄色网 | 日韩精品一区二区在线观看视频 | 国产精彩视频一区 | 综合色综合 | 97国产精品| 国产精品久久在线观看 | 成人三级网站在线观看 | 久久免费一级片 | 97涩涩视频| 久久天天躁夜夜躁狠狠85麻豆 | 久久伦理网 | 丝袜美腿在线视频 | 久久久久久久久久久久国产精品 | 99色99| 九九色在线观看 | 中文字幕丝袜美腿 | 在线观看黄色 | 国产精品video爽爽爽爽 | 国产美女视频免费 | 色偷偷中文字幕 | 中文字幕在线视频一区 | se视频网址 | 最新av在线免费观看 | 久久久久免费视频 | 911国产在线观看 | 激情网色 | 亚洲人成影院在线 | 精品国产乱码久久久久久天美 | 综合网天天射 | 日日夜夜精品视频天天综合网 | 国产麻豆精品95视频 | 999久久久免费精品国产 | 亚洲人人av | 欧美午夜a| 99免在线观看免费视频高清 | 国产精品日韩欧美一区二区 | 国产视频一区在线 | 国内精品久久久久久 | 日本3级在线观看 | 久久成人在线 | 视频91在线 | 亚洲成人黄色 | 久久精品美女 | 成人91av| 久久久久久97三级 | 欧美xxxx性xxxxx高清 | 免费在线成人av电影 | 久草在线一免费新视频 | 久久综合爱 | 国产精品va在线观看入 | 在线观看国产永久免费视频 | 精品久久免费 | 国产视频在线观看一区 | 国产在线一线 | 国产精品中文在线 | 国产成人一级 | av直接看 | 亚洲精品美女久久久 | 999日韩| av中文字幕不卡 | 日韩欧美在线综合网 | 人成午夜视频 | 九九久久精品 | 日本精品一区二区三区在线播放视频 | 九九免费在线观看视频 | 天天操天天色天天射 | 亚洲毛片视频 | 天天爱天天色 | 欧洲色综合 | 91网站免费观看 | 最新国产精品拍自在线播放 | 久久久久久久国产精品影院 | 激情婷婷欧美 | 欧美一二三区播放 | 日韩在线视频线视频免费网站 | 免费黄色a级毛片 | 久久综合狠狠综合久久狠狠色综合 | 国产日韩欧美在线免费观看 | 激情久久久 | 人人插人人费 | 在线一区电影 | 亚洲全部视频 | 亚洲丁香日韩 | 成人av电影免费观看 | 99视频国产精品 | 国产一区网址 | 久久久国产精品一区二区中文 | 中文字幕在线网址 | 欧美性久久久久久 | h动漫中文字幕 | 亚洲精品在线一区二区 | 中文字幕在线观看免费观看 | 国产成人精品久 | 欧美久久久久久久久久 | 在线 欧美 日韩 | 在线看小早川怜子av | 国产黄色av网站 | 中国一级片免费看 | 成人资源在线播放 | 麻豆免费看片 | 亚洲另类视频在线观看 | 久久综合加勒比 | 欧美久久久一区二区三区 | 中文字幕电影一区 | 香蕉网站在线观看 | 亚州av免费| 日韩三级在线 | 精品视频一区在线观看 | 国产 欧美 在线 | 久久久久久久久久免费 | 欧美精品一区二区三区四区在线 | 91成人在线视频 | 欧美一区二区日韩一区二区 | 在线免费观看的av网站 | 成年人黄色免费看 | 黄色在线免费观看网站 | 伊人天堂网 | 国产精品99久久久久久人免费 | 久久人人爽爽人人爽人人片av | 涩涩成人在线 | 久久免费视频这里只有精品 | 国产精品久久久久av免费 | 国产理论免费 | 亚洲国产精品视频在线观看 | 99免费看片 | 97人人澡人人爽人人模亚洲 | 大胆欧美gogo免费视频一二区 | 三级视频片 | 成人av一区二区兰花在线播放 | 欧美a级在线免费观看 | 国产成人在线精品 | 国产成人精品一区二区三区 | 国产精品成人一区二区三区吃奶 | 国产精品亚洲a | 久久无码精品一区二区三区 | 精油按摩av | 蜜臀精品久久久久久蜜臀 | 香蕉97视频观看在线观看 | 成 人 黄 色 视频免费播放 | 国产精品一码二码三码在线 | 狠狠操操| 日日夜夜精品视频天天综合网 | 婷婷六月综合亚洲 | 九九亚洲视频 | 日本最新高清不卡中文字幕 | 91热视频在线观看 | 国产精品v欧美精品 | 亚洲乱亚洲乱亚洲 | 日韩精品一区二区三区不卡 | 欧美在线18| 99视频在线精品国自产拍免费观看 | 午夜手机电影 | 在线观看国产永久免费视频 | 日韩视频在线观看免费 | 免费看成年人 | 亚洲视频在线免费看 | 高清国产午夜精品久久久久久 | 久久国色夜色精品国产 | 午夜精品福利影院 | 国产亚洲精品久久久久久久久久 | 亚洲成人av片在线观看 | 欧美日韩精品电影 | 综合色亚洲 | 婷婷激情综合五月天 | 黄色小网站在线观看 | 国产亚洲综合在线 | 久久不卡电影 | 久久久精选 | 久久久久久久久久久久久久电影 | 亚洲精品视频中文字幕 | 狠狠干婷婷色 | 亚洲国产精品视频在线观看 | 久久久精品一区二区三区 | av再线观看| 激情综合狠狠 | 99亚洲精品 | 久久久亚洲精品 | 特级片免费看 | 国产中文在线字幕 | 91看片淫黄大片一级在线观看 | 亚洲精品美女免费 | 久久久69 | 日日日视频 | 国产精久久 | 日韩免费在线观看网站 | 成人av电影免费观看 | 国内外成人在线 | 九九99靖品 | 四虎成人av | 国产精品久久久影视 | 91在线精品秘密一区二区 | 免费中午字幕无吗 | 天天干天天干天天干 | 香蕉影视 | 在线看毛片网站 | 久久久久久久久久久高潮一区二区 | 国产电影一区二区三区四区 | 国产99在线 | 久久99免费 | 久久一久久 | 成人在线观看日韩 | 美女网站久久 | 91精品国产综合久久福利不卡 | 黄色资源在线观看 | 久久久免费播放 | 久久久久久久久久久影视 | 久久999精品| 国产.精品.日韩.另类.中文.在线.播放 | 久久久久国| 婷婷在线播放 | 91丨九色丨蝌蚪丨老版 | 日韩试看 | 午夜久草| 九九亚洲视频 | 欧美人体xx | 久久视频中文字幕 | 欧美日韩视频免费看 | 亚洲乱码久久久 | 97成人在线免费视频 | 国产在线永久 | 国产日韩精品一区二区三区 | 久久99国产精品免费 | 久草免费在线视频 | 国产精品99久久99久久久二8 | 国产69精品久久久久久久久久 | 中文av日韩 | 五月婷婷丁香激情 | 久草成人在线 | 欧美久久久久久久久久 | 色综合夜色一区 | 国内外成人在线视频 | 久久人人爽人人爽人人片av软件 | 免费h视频 | 亚洲国产精品500在线观看 | 国产xx在线 | 国产在线精品国自产拍影院 | 免费国产在线观看 | 91女人18片女毛片60分钟 | 午夜性盈盈 | 玖玖国产精品视频 | 国产亚洲精品久久久久久久久久 | 黄色a一级视频 | 亚洲 欧美 日韩 综合 | 国内精品久久久久影院男同志 | 国产 亚洲 欧美 在线 | 国产99色| 亚洲一区二区三区miaa149 | 国产精品男女视频 | 97超碰国产在线 | 国产r级在线观看 | 在线不卡的av | 欧美精品视 | 中文字幕在线视频网站 | 玖玖在线资源 | 日韩欧美在线高清 | 成人免费xyz网站 | 久久国内精品 | 婷婷在线视频观看 | 香蕉国产91| 中国一级特黄毛片大片久久 | 丁香花在线视频观看免费 | 国产精品成人一区 | 国产成人中文字幕 | www久久久久 | av免费看网站 | 九九九国产 | 成人a在线观看高清电影 | 激情五月***国产精品 | 日本不卡一区二区三区在线观看 | 日韩三级视频在线观看 | 国产超碰在线 | 丝袜+亚洲+另类+欧美+变态 | 欧美高清成人 | 日本在线观看一区二区 | 爱色av.com| 色偷偷人人澡久久超碰69 | 国产精品午夜在线观看 | 高清在线一区 | 国产不卡在线观看视频 | 国内精品久久影院 | 青草视频在线 | 亚洲视频观看 | 国产亚洲精品成人av久久ww | 免费a级毛片在线看 | japanesexxxhd奶水 91在线精品一区二区 | 久久网页 | 四虎在线永久免费观看 | 天天操天天干天天插 | 99热99热 | 91在线操 | av网站地址| 人人干人人做 | 免费精品人在线二线三线 | 五月婷婷操 | 777久久久| 日韩欧美高清视频在线观看 | 免费a v视频| 精品国产一区二 | 在线观看视频97 | 久久精品一二三区白丝高潮 | 日韩电影在线一区二区 | 正在播放 久久 | 亚洲乱码久久 | 91成人在线视频 | 国产一级片播放 | 久久高清免费视频 | a在线v| 九九热在线观看视频 | 国产日韩精品在线 | 免费视频黄色 | 日韩成人欧美 | 国产精品久久久久久久久久久不卡 | 久热电影 | 玖玖国产精品视频 | 在线v片免费观看视频 | 中文亚洲欧美日韩 | 久久久久亚洲国产精品 | 激情视频久久 | 日韩欧美国产免费播放 | 综合精品在线 | 美国三级黄色大片 | 国产91九色视频 | 久久精品亚洲精品国产欧美 | 天天操天天干天天 | 成年人在线免费看 | 97影视| 涩涩网站在线看 | 日日日爽爽爽 | 中文字幕乱码在线播放 | 在线观看小视频 | 国产成人精品一区二区三区免费 | 99国产精品久久久久老师 | 久久精品国产v日韩v亚洲 | av中文天堂在线 | 不卡av免费在线观看 | 国产一区二区在线观看视频 | 国产在线观看av | 国产精品网红福利 | 激情伊人五月天久久综合 | 日日干夜夜操视频 | 波多野结衣精品在线 | 日本久久久久久科技有限公司 | 天天操天天摸天天干 | 亚洲激情在线视频 | 国产精品国产三级国产aⅴ入口 | 亚洲国产小视频在线观看 | 国产经典av | 黄色大片av | 操操操人人人 | 国产一区视频在线观看免费 | 日本少妇视频 | 久久九九久久 | 日韩欧美在线第一页 | 青草视频在线播放 | 天天天天综合 | 麻豆系列在线观看 | 91福利视频免费观看 | 久久精品一区二区三区中文字幕 | 狠狠色网 | 久久尤物电影视频在线观看 | a亚洲视频 | 免费视频久久久久久久 | 久久草草影视免费网 | 亚洲激情精品 | 91在线看视频 | 亚洲最新视频在线 | 99在线热播精品免费 | 二区视频在线观看 | 亚洲国产99 | 丁香视频免费观看 | 成人免费视频播放 | 91香蕉国产在线观看软件 | 成人免费在线播放 | 天天操天天爱天天干 | 国产精品igao视频网入口 | 日韩精品一区二区在线视频 | 国产香蕉视频在线播放 | 成人丁香花 | 欧美日一级片 | 国产色视频123区 | 久久午夜精品视频 | 又紧又大又爽精品一区二区 | 欧美乱大交| 黄色aaa级片 | 亚洲国产免费看 | 黄色一级大片在线免费看产 | 久久久国产精品一区二区中文 | 日韩久久久久久久久久 | 亚洲aaa级 | 四虎8848免费高清在线观看 | 99精品观看 | 天天做综合网 | 99性视频 | 在线免费高清视频 | 成年人天堂com | 天天操天天能 | 精品久久久久久久久久岛国gif | 在线观看免费一级片 | 国产黄色免费看 | 色噜噜狠狠色综合中国 | 麻豆精品传媒视频 | 亚洲人成人在线 | 五月激情天 | 日日躁夜夜躁xxxxaaaa | 99福利影院| 国产精品2019 | 亚洲免费视频在线观看 | 日韩mv欧美mv国产精品 | 日日日日日 | 五月婷婷视频在线 | 欧美激情xxxx | 日日射天天射 | 成人羞羞免费 | 久久精品国产久精国产 | 亚洲综合精品在线 | 欧美91片| 97成人在线观看 | 日韩综合第一页 | 国产精品久久久久毛片大屁完整版 | 国产亚洲精品久久久久久大师 | 91九色porny蝌蚪主页 | 久久久午夜精品福利内容 | 又黄又刺激 | 国产精品麻豆免费版 | 亚洲综合导航 | 一区二区三区福利 | av在线播放国产 | 欧美一级黄大片 | 精品久久久免费视频 | 91精品视频免费在线观看 | 欧美一区二区三区四区夜夜大片 | 日韩免费在线视频观看 | 欧美日韩在线免费观看 | 精品国产一区二区三区久久久蜜月 | 五月天婷婷在线观看视频 | 日本丰满少妇免费一区 | 亚洲国产97在线精品一区 | 久久99久久99免费视频 | 福利一区二区 | 久爱精品在线 | 成人不用播放器 | 国内精品毛片 | 亚洲午夜精品在线观看 | 蜜桃视频在线视频 | 九月婷婷色 | 韩国av电影网 | 欧美福利网站 | 亚洲黄色免费电影 | 国产精品免费久久久久影院仙踪林 | 久久亚洲欧美 | 日本丰满少妇免费一区 | 日韩大陆欧美高清视频区 | 国产在线观看av | 日韩av女优视频 | 91中文字幕在线 | 久久一区二区三区国产精品 | 日韩天堂网 | 啪一啪在线 | 黄色软件大全网站 | 9在线观看免费高清完整版在线观看明 | 欧美aⅴ在线观看 | 日本成人中文字幕在线观看 | 色播五月激情综合网 | 欧美a级在线免费观看 | a级片在线播放 | 国产第一福利网 | 婷婷亚洲五月色综合 | 色一级片 | 亚洲午夜av久久乱码 | 精品不卡av| 国产99久久精品一区二区300 | 国产999精品久久久久久 | 91精品亚洲影视在线观看 | 天天草天天爽 | 97天堂网 | 国产高清福利在线 | 色综合天天在线 | 日韩精品中文字幕在线观看 | 国内精品亚洲 | 欧美日韩免费一区 | 精品国产1区2区 | a级片网站| 天天综合天天综合 | 久久精品视频4 | 国产在线观看高清视频 | 色综合色综合久久综合频道88 | 亚洲欧美日韩一二三区 | 青草视频网 | 亚洲精色 | 西西444www | 日韩天堂网| 亚洲最快最全在线视频 | 99这里只有久久精品视频 | 国产成人精品午夜在线播放 | 国产精品原创 | 超碰在97 | 手机av资源| 国产在线观看地址 | 丁香婷婷激情国产高清秒播 | 国产色久 | 国产精品视频免费在线观看 | 中文字幕中文字幕 | 8x成人在线 | 免费电影播放 | 日本精品一区二区三区在线播放视频 | 国产精品久久久久一区二区三区 | 97精品超碰一区二区三区 | 一区二区精品在线 | 国产传媒中文字幕 | 国产99色 | 99久久婷婷国产综合精品 | 成人全视频免费观看在线看 | 国产亚洲精品精品精品 | 男女激情免费网站 | 成在线播放| 国产精品一区二 | 狠狠躁夜夜av | 在线免费黄色 | 国产一区欧美在线 | 日韩精品一区二区三区外面 | 欧美激情xxxx性bbbb | 日韩电影中文字幕在线 | 久久精品视频观看 | 精品在线亚洲视频 | 97在线精品国自产拍中文 | 欧美日韩国产精品一区二区三区 | 中文字幕.av.在线 | www.人人草 | 香蕉网在线观看 | 国产美女网站在线观看 | 91av视频在线免费观看 | 视频91| 国产精品每日更新 | 五月天激情综合网 | 国产在线观看免费 | 久久精品国产精品 | 天天艹| 成年人网站免费观看 | 久久伊人色综合 | 国产日本三级 | 天天干天天干天天干 | 国产一级免费播放 | 亚洲精品国产精品国产 | 香蕉视频免费在线播放 | 天天操天天舔天天干 | 免费看高清毛片 | 日韩三级视频在线观看 | 香蕉精品在线观看 | 国产在线观看中文字幕 | 韩日成人av | 久久只有精品 | 日韩天天干| 日本久久免费电影 | 91精品国产91久久久久久三级 | 99久久久国产精品免费99 | 草免费视频 | 成人动漫精品一区二区 | 奇米网444 | 九九热只有精品 | 国产精品国产三级在线专区 | 欧美最新另类人妖 | 免费国产黄线在线观看视频 | 天天玩天天操天天射 | 91mv.cool在线观看 | 美女黄频在线观看 | 中文字幕在线一区二区三区 | 午夜免费电影院 | 国产区网址 | h动漫中文字幕 | 亚洲 精品在线视频 | 久久这里只有精品久久 | 久久不射影院 | 欧洲激情在线 | www.大网伊人 | 日韩字幕在线观看 | 深爱开心激情网 | 欧日韩在线| 4438全国亚洲精品在线观看视频 | 99一级片| 国产精品嫩草55av | 国产日韩精品一区二区三区 | 国产精品久久久久久久久久了 | 中文字幕在线网 | 国产精品私人影院 | 国产精品一区二区三区99 | 欧美精品xx | 精品国产乱子伦一区二区 | 国产成人黄色在线 | 99久久夜色精品国产亚洲 | 精品久久一区二区 | www.亚洲精品视频 | 免费国产在线精品 | 欧美另类xxx | 激情av资源| 成人影音av | 91爱爱网址 | 中日韩欧美精彩视频 | 久久伊人爱| 国产精品欧美日韩在线观看 | 欧美成亚洲 | 美女精品国产 | 久久国产精品99久久久久久进口 | 精品国产a | 婷婷激情5月天 | 亚洲国产网站 | 国内精品视频一区二区三区八戒 | 国产精品毛片完整版 | 伊人成人久久 | 一区二区三区四区五区六区 | 日韩在线观看一区二区 | 欧美影片| 欧美性生活免费看 | 伊人色播| 中文字幕色综合网 | 福利视频一二区 | 日韩三级视频在线观看 | 狠狠精品 | 色婷婷亚洲综合 | 亚洲综合成人专区片 | 99热只有精品在线观看 | 五月天电影免费在线观看一区 | 免费91麻豆精品国产自产在线观看 | 日韩欧美网站 | 亚洲精品无 | 日韩精品一区二区三区免费视频观看 | 亚洲综合一区二区精品导航 | 久久黄色影视 | 欧美精品免费一区二区 | 日韩免费专区 | 久久精品免视看 | 免费看国产曰批40分钟 | 日韩欧美视频一区二区三区 | 国产一区二区三区免费在线观看 | 久久精品国产成人精品 | 日韩激情视频在线观看 | 精品在线视频观看 | 人人爱人人做人人爽 | 99热手机在线 | 中文有码在线视频 | 久久精品香蕉视频 | 欧美精品久久久久久久亚洲调教 | 国产分类视频 | 久久国产视屏 | 色婷婷啪啪免费在线电影观看 | 亚洲欧美日韩一二三区 | 日韩最新在线视频 | 少妇视频一区 | 国产精品成人a免费观看 | 国产免费影院 | 黄色影院在线播放 | 久草在线最新视频 | 在线观看视频一区二区三区 | 美女免费视频黄 | 成人黄色在线视频 | 97色在线观看免费视频 | 美女久久久久久久 | 免费看三级黄色片 | 美女福利视频一区二区 | 999国内精品永久免费视频 | 亚洲天堂网站 | 国产精品久久久久永久免费看 | 久热久草在线 | 国产婷婷精品av在线 | 免费观看www7722午夜电影 | 五月婷婷在线播放 | 免费a现在观看 | 日本资源中文字幕在线 | 久久人网 | 综合网欧美 | 亚洲三级在线免费观看 | 久久久久黄色 | 欧美一级片在线免费观看 | 欧美在线1区| 精品在线99 | 久久午夜免费视频 | 97在线视频免费观看 | 天天躁天天狠天天透 | 免费在线观看日韩视频 | 日韩高清一区在线 | 欧美日韩国产精品一区二区亚洲 | 国产成人免费在线观看 | 91中文字幕| 国产高清精品在线观看 | 一区二区三区中文字幕在线 | 中文字幕高清免费日韩视频在线 | 精品国产电影一区二区 | 欧美日韩一区二区在线观看 | av九九九 | 国产高清在线观看 | 天天操天天射天天 | 久久精品视频在线观看免费 | 国产123区在线观看 国产精品麻豆91 | 亚洲天天在线日亚洲洲精 | 97精品国自产拍在线观看 | 色干干| 69国产成人综合久久精品欧美 | 狠狠干成人综合网 | 天天色图 | 亚洲黄色在线播放 | 国产一级二级三级在线观看 | 日韩区欠美精品av视频 | 日韩a级黄色 | 国产一区二区三区在线免费观看 | 中文视频在线看 | 欧美日韩一区二区三区在线观看视频 | 精品视频免费 | 热九九精品 | 狠狠综合久久 | 欧美在线一二区 | 天天干天天干天天 | 久久久久久久久久久福利 | 日韩在线三区 | 国产一区私人高清影院 | 国内三级在线 | 久久国产精品视频观看 | 日韩免费一区二区在线观看 | 91精品在线视频 | 国产精品久久人 | 日日操日日 | 欧美性护士| 日韩激情在线 | 最近的中文字幕大全免费版 | 在线亚洲成人 | 91在线91拍拍在线91 | 久久久久久久久久久久亚洲 | 亚洲精品18p | 少妇性aaaaaaaaa视频 | 国产一区免费在线观看 | 波多野结衣在线播放一区 | 成年人免费av | 亚洲天天综合网 | 蜜臀av夜夜澡人人爽人人桃色 | 最近中文字幕完整高清 | 中文字幕国产精品一区二区 | 91看片一区二区三区 | 国产精品第52页 | 99久热在线精品视频成人一区 | 在线观看视频免费大全 | 国产原创在线 | 色午夜影院 | 国产手机在线观看视频 | 在线观看黄网站 | 99视频+国产日韩欧美 | 91中文字幕永久在线 | 中文字幕在线观看视频免费 | 一本一道波多野毛片中文在线 | 美女黄视频免费 | 久久精品视 | 中文字幕在线免费 | 在线最新av | 夜夜干夜夜 | 国产不卡视频在线播放 | 亚洲电影第一页av | 精品久久久999 | 日韩色中色 | 91精品一区二区三区蜜桃 | 91精品系列| 日韩精品免费一区二区在线观看 | 伊人激情网| 久久精品8 | 亚洲精品久久久久久中文传媒 | 欧美伦理一区 | 婷婷精品国产一区二区三区日韩 | 日韩欧美一区二区在线 | 欧美日韩久久 | 精品国产一区在线观看 | 麻豆影视在线观看 | 九九久久久久久久久激情 | 草久草久| 久久综合久久综合这里只有精品 | 四虎免费av| 免费精品视频在线观看 | 欧美一二三专区 | 久久不射影院 | 三级黄在线| 国产在线a免费观看 | 伊人久久五月天 | 国产精品丝袜 | 在线观看视频h | 精品福利视频在线观看 | 久久国产品 | 91麻豆精品国产91 | 在线看片视频 | 天天摸夜夜操 | 在线观看精品一区 | 三级黄色网址 | 国产亚洲精品久 | 91福利国产在线观看 | 欧美视频网址 | 国产色婷婷 | 日韩av手机在线观看 | 中文字幕黄色网址 | 国产韩国日本高清视频 | 久久一级片 | 超碰日韩在线 | 免费在线日韩 | 操操综合 | 天天射天天操天天 | 高清av网| 欧美激情va永久在线播放 | 色a资源在线 | 国产视频丨精品|在线观看 国产精品久久久久久久久久久久午夜 | 久久成年人 | 在线观看视频在线观看 | 欧美成人tv | 日本久久成人中文字幕电影 | 久久亚洲欧美 | 日韩天堂在线观看 | 欧美另类成人 | 天天狠狠干 | 午夜资源站 | 久久久久久久99精品免费观看 | 国产人成在线视频 | 亚洲成人资源在线观看 | 日韩久久视频 | 五月激情电影 | 97精品在线观看 | 亚洲精品在线视频播放 | 日韩精品久久中文字幕 | 久久图 | 国产精品国产亚洲精品看不卡15 | .国产精品成人自产拍在线观看6 | 亚州天堂| 狠狠干成人综合网 | 国语自产偷拍精品视频偷 | 色干干| 狠狠干电影 | 亚洲精品国产电影 | 久久伊99综合婷婷久久伊 | 成人黄色av网站 | 色综合久 | 国产二区视频在线观看 | 国产日韩欧美自拍 | 中文视频在线看 | 欧美日韩亚洲一 | www免费视频com━ | 91久久国产自产拍夜夜嗨 | 国产精品久久久久久久妇 | 免费高清无人区完整版 | 视频在线在亚洲 | 精品一区二区电影 | 日韩有码在线观看视频 | 91精品国产成人www | 深爱五月激情网 | 92国产精品久久久久首页 | 91久久国产综合精品女同国语 | 美女福利视频网 | 青青草在久久免费久久免费 | 成人av网站在线 | 久久国际影院 | 蜜臀av性久久久久蜜臀av | 91重口视频 | 日韩一区二区三区免费视频 | 一区二区三区四区免费视频 | 日韩在线中文字幕 | 国产精品久久久久久久电影 | 中文字幕在线观看第三页 | 免费影视大全推荐 | 久久综合免费视频影院 | 五月婷婷综合激情网 | 日韩免费在线观看视频 | 亚洲午夜久久久影院 | 亚洲理论电影网 | 国产二区视频在线 | av在线亚洲天堂 | 黄色一级大片在线免费看国产一 | 亚洲理论视频 | 欧美日韩aa| 亚洲精品大全 | 日韩精品短视频 | 中文不卡视频在线 | 中文字幕免费高清 | 国产成人精品久久久久蜜臀 | 亚洲精品系列 | 成人亚洲免费 | 字幕网av | 亚洲国产wwwccc36天堂 | 特级毛片网| 亚洲综合在线视频 | 婷婷色六月天 | 99久精品视频| a视频免费在线观看 | 玖玖国产精品视频 | www.久久视频 | 国产精品久久久久久久久久妇女 | 日本在线观看视频一区 | www黄色av | 日韩av视屏在线观看 | 亚洲最大成人网4388xx | 西西44人体做爰大胆视频 | 亚洲经典视频在线观看 | 中文字幕在线观看的网站 | 亚洲男模gay裸体gay | 射综合网 | 国产视频精品久久 | 国产精品久久久久久一区二区三区 | 91在线免费播放 | 日韩电影一区二区在线观看 | av中文在线播放 | 99爱在线| 久久99久国产精品黄毛片入口 | 亚洲一区视频在线播放 | 在线看片一区 | 最近中文字幕大全 | 久久久精品日本 | 精品v亚洲v欧美v高清v | 日本在线视频一区二区三区 | 伊人久久在线观看 | 国产一区福利在线 | 国产一区观看 | 国产精品美 | 涩涩网站在线观看 | 在线v片免费观看视频 | 久久手机看片 | 国产精品系列在线观看 | 夜夜天天干 | 蜜桃视频在线观看一区 | 欧美成年人在线观看 | 成人一级视频在线观看 | 日韩在线免费小视频 | 天天天天天干 | 亚洲视频www| 亚洲免费永久精品国产 | 天天拍天天干 | 高清有码中文字幕 | 天天射综合网站 | 欧美一区二区三区免费看 | 天堂av一区二区 | 91麻豆精品国产91久久久使用方法 | 一区在线免费观看 | 久久综合九色综合网站 | 国产对白av | 久久久久影视 | 日本99干网 | 亚洲女人天堂成人av在线 | 在线视频一区二区 | 在线观看视频在线观看 | 精品国产欧美一区二区三区不卡 | 一区二区亚洲精品 | 日韩在线视频在线观看 | 曰本免费av | 免费在线观看av网址 | se婷婷 | v片在线播放 |