<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                # Event Loop詳解 點擊關注本[公眾號](http://www.hmoore.net/book/dsh225/javascript_vue_css/edit#_118)獲取文檔最新更新,并可以領取配套于本指南的《**前端面試手冊**》以及**最標準的簡歷模板**. > 本文是[弄懂Event Loop](https://juejin.im/post/5c3d8956e51d4511dc72c200?utm_source=gold_browser_extension#comment)的刪改版,去除了原文中一些容易引起歧義的部分,對一些內容進行了擴充 [TOC] ## 前言 `Event Loop`即事件循環,是指瀏覽器或`Node`的一種解決`javaScript`單線程運行時不會阻塞的一種機制,也就是我們經常使用**異步**的原理。 ## 為啥要弄懂Event Loop * 是要增加自己技術的深度,也就是懂得`JavaScript`的運行機制。 * 現在在前端領域各種技術層出不窮,掌握底層原理,可以讓自己以不變,應萬變。 * 應對各大互聯網公司的面試,懂其原理,題目任其發揮。 ## 棧、隊列的基本概念 ![](https://cdn.nlark.com/yuque/0/2019/webp/128853/1560995437042-9d683636-9bf5-45fb-8cf3-e482b94a707d.webp#align=left&display=inline&height=271&originHeight=271&originWidth=294&size=0&status=done&width=294) ### 棧(Stack) **棧**在計算機科學中是限定僅在**表尾**進行**插入**或**刪除**操作的線性表。?**棧**是一種數據結構,它按照**后進先出**的原則存儲數據,**先進入**的數據被壓入**棧底**,**最后的數據**在**棧頂**,需要讀數據的時候從**棧頂**開始**彈出數據**。 **棧**是只能在**某一端插入**和**刪除**的**特殊線性表**。 ![](https://cdn.nlark.com/yuque/0/2019/webp/128853/1560995436902-6dcf8420-be5b-4dd9-9fb6-43e567e53c86.webp#align=left&display=inline&height=282&originHeight=282&originWidth=616&size=0&status=done&width=616) ### 隊列(Queue) 特殊之處在于它只允許在表的前端(`front`)進行**刪除**操作,而在表的后端(`rear`)進行**插入**操作,和**棧**一樣,**隊列**是一種操作受限制的線性表。 進行**插入**操作的端稱為**隊尾**,進行**刪除**操作的端稱為**隊頭**。 隊列中沒有元素時,稱為**空隊列**。 **隊列**的數據元素又稱為**隊列元素**。在隊列中插入一個隊列元素稱為**入隊**,從**隊列**中**刪除**一個隊列元素稱為**出隊**。因為隊列**只允許**在一端**插入**,在另一端**刪除**,所以只有**最早**進入**隊列**的元素**才能最先從隊列中**刪除,故隊列又稱為**先進先出**(`FIFO—first in first out`) ![](https://cdn.nlark.com/yuque/0/2019/webp/128853/1560995436932-57cb6ee5-763a-47b2-a0be-4174c4fd1c66.webp#align=left&display=inline&height=270&originHeight=270&originWidth=554&size=0&status=done&width=554) ## Event Loop 在`JavaScript`中,任務被分為兩種,一種宏任務(`MacroTask`)也叫`Task`,一種叫微任務(`MicroTask`)。 ### (宏任務) * `script`全部代碼、`setTimeout`、`setInterval`、`setImmediate`(瀏覽器暫時不支持,只有IE10支持,具體可見[`MDN`](https://link.juejin.im/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FWindow%2FsetImmediate))、`I/O`、`UI Rendering`。 ### (微任務) * `Process.nextTick(Node獨有)`、`Promise`、`Object.observe(廢棄)`、`MutationObserver`(具體使用方式查看[這里](https://link.juejin.im/?target=http%3A%2F%2Fjavascript.ruanyifeng.com%2Fdom%2Fmutationobserver.html)) ## 瀏覽器中的Event Loop `Javascript`?有一個?`main thread`?主線程和?`call-stack`?調用棧(執行棧),所有的任務都會被放到調用棧等待主線程執行。 ### JS調用棧 JS調用棧采用的是后進先出的規則,當函數執行的時候,會被添加到棧的頂部,當執行棧執行完成后,就會從棧頂移出,直到棧內被清空。 ### 同步任務和異步任務 `Javascript`單線程任務被分為**同步任務**和**異步任務**,同步任務會在調用棧中按照順序等待主線程依次執行,異步任務會在異步任務有了結果后,將注冊的回調函數放入任務隊列中等待主線程空閑的時候(調用棧被清空),被讀取到棧內等待主線程的執行。 ![](https://cdn.nlark.com/yuque/0/2019/webp/128853/1560995436925-9ac8fb38-4dfd-4d12-b5fe-c564ede43f80.webp#align=left&display=inline&height=518&originHeight=518&originWidth=636&size=0&status=done&width=636)任務隊列`Task Queue`,即隊列,是一種先進先出的一種數據結構。![](https://cdn.nlark.com/yuque/0/2019/webp/128853/1560995437021-43545b5b-0a48-475a-a8a6-cce80433fe1a.webp#align=left&display=inline&height=669&originHeight=669&originWidth=800&size=0&status=done&width=800) ### 事件循環的進程模型 * 選擇當前要執行的任務隊列,選擇任務隊列中最先進入的任務,如果任務隊列為空即`null`,則執行跳轉到微任務(`MicroTask`)的執行步驟。 * 將事件循環中的任務設置為已選擇任務。 * 執行任務。 * 將事件循環中當前運行任務設置為null。 * 將已經運行完成的任務從任務隊列中刪除。 * microtasks步驟:進入microtask檢查點。 * 更新界面渲染。 * 返回第一步。 ### 執行進入microtask檢查點時,用戶代理會執行以下步驟: * 設置microtask檢查點標志為true。 * 當事件循環`microtask`執行不為空時:選擇一個最先進入的`microtask`隊列的`microtask`,將事件循環的`microtask`設置為已選擇的`microtask`,運行`microtask`,將已經執行完成的`microtask`為`null`,移出`microtask`中的`microtask`。 * 清理IndexDB事務 * 設置進入microtask檢查點的標志為false。 上述可能不太好理解,下圖是我做的一張圖片。 ![](https://cdn.nlark.com/yuque/0/2019/gif/128853/1560995436931-71f56a41-54d3-49f3-a382-c1e6acbf301e.gif#align=left&display=inline&height=589&originHeight=589&originWidth=1011&size=0&status=done&width=1011) 執行棧在執行完**同步任務**后,查看**執行棧**是否為空,如果執行棧為空,就會去檢查**微任務**(`microTask`)隊列是否為空,如果為空的話,就執行`Task`(宏任務),否則就一次性執行完所有微任務。 每次單個**宏任務**執行完畢后,檢查**微任務**(`microTask`)隊列是否為空,如果不為空的話,會按照**先入先**出的規則全部執行完**微任務**(`microTask`)后,設置**微任務**(`microTask`)隊列為`null`,然后再執行**宏任務**,如此循環。 ## 舉個例子 ~~~ console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end'); ~~~ 首先我們劃分幾個分類: ### 第一次執行: ~~~ Tasks:run script、 setTimeout callback Microtasks:Promise then JS stack: script Log: script start、script end。 ~~~ 執行同步代碼,將宏任務(`Tasks`)和微任務(`Microtasks`)劃分到各自隊列中。 ### 第二次執行: ~~~ Tasks:run script、 setTimeout callback Microtasks:Promise2 then JS stack: Promise2 callback Log: script start、script end、promise1、promise2 ~~~ 執行宏任務后,檢測到微任務(`Microtasks`)隊列中不為空,執行`Promise1`,執行完成`Promise1`后,調用`Promise2.then`,放入微任務(`Microtasks`)隊列中,再執行`Promise2.then`。 ### 第三次執行: ~~~ Tasks:setTimeout callback Microtasks: JS stack: setTimeout callback Log: script start、script end、promise1、promise2、setTimeout ~~~ 當微任務(`Microtasks`)隊列中為空時,執行宏任務(`Tasks`),執行`setTimeout callback`,打印日志。 ### 第四次執行: ~~~ Tasks:setTimeout callback Microtasks: JS stack: Log: script start、script end、promise1、promise2、setTimeout ~~~ 清空**Tasks**隊列和`JS stack`。 以上執行幀動畫可以查看[Tasks, microtasks, queues and schedules](https://link.juejin.im/?target=https%3A%2F%2Fjakearchibald.com%2F2015%2Ftasks-microtasks-queues-and-schedules%2F) 或許這張圖也更好理解些。 ![](https://cdn.nlark.com/yuque/0/2019/gif/128853/1560995436968-c6ff2732-4b20-49d4-852f-8c298eeb0d2e.gif#align=left&display=inline&height=341&originHeight=341&originWidth=611&size=0&status=done&width=611) ## 再舉個例子 ~~~ console.log('script start') async function async1() { await async2() console.log('async1 end') } async function async2() { console.log('async2 end') } async1() setTimeout(function() { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) .then(function() { console.log('promise2') }) console.log('script end') ~~~ 這里需要先理解`async/await`。 `async/await`?在底層轉換成了?`promise`?和?`then`?回調函數。 也就是說,這是?`promise`?的語法糖。 每次我們使用?`await`, 解釋器都創建一個?`promise`?對象,然后把剩下的?`async`?函數中的操作放到?`then`?回調函數中。 `async/await`?的實現,離不開?`Promise`。從字面意思來理解,`async`?是“異步”的簡寫,而?`await`?是?`async wait`?的簡寫可以認為是等待異步方法執行完成。 ### **關于73以下版本和73版本的區別** * 在老版本版本以下,先執行`promise1`和`promise2`,再執行`async1`。 * 在73版本,先執行`async1`再執行`promise1`和`promise2`。 **主要原因是因為在谷歌(金絲雀)73版本中更改了規范,如下圖所示**: ![](https://cdn.nlark.com/yuque/0/2019/webp/128853/1560995436973-49daab42-3959-4cb2-9417-30a651fedf80.webp#align=left&display=inline&height=243&originHeight=243&originWidth=668&size=0&status=done&width=668) * 區別在于`RESOLVE(thenable)`和之間的區別`Promise.resolve(thenable)`。 ### **在老版本中** * 首先,傳遞給?`await`?的值被包裹在一個?`Promise`?中。然后,處理程序附加到這個包裝的?`Promise`,以便在?`Promise`?變為?`fulfilled`?后恢復該函數,并且暫停執行異步函數,一旦?`promise`?變為?`fulfilled`,恢復異步函數的執行。 * 每個?`await`?引擎必須創建兩個額外的 Promise(即使右側已經是一個?`Promise`)并且它需要至少三個?`microtask`?隊列?`ticks`(`tick`為系統的相對時間單位,也被稱為系統的時基,來源于定時器的周期性中斷(輸出脈沖),一次中斷表示一個`tick`,也被稱做一個“時鐘滴答”、時標。)。 ### **引用賀老師知乎上的一個例子** ~~~ async function f() { await p console.log('ok') } ~~~ 簡化理解為: ~~~ function f() { return RESOLVE(p).then(() => { console.log('ok') }) } ~~~ * 如果?`RESOLVE(p)`?對于?`p`?為?`promise`?直接返回?`p`?的話,那么?`p`的?`then`?方法就會被馬上調用,其回調就立即進入?`job`?隊列。 * 而如果?`RESOLVE(p)`?嚴格按照標準,應該是產生一個新的?`promise`,盡管該?`promise`確定會?`resolve`?為?`p`,但這個過程本身是異步的,也就是現在進入?`job`?隊列的是新?`promise`的?`resolve`過程,所以該?`promise`?的?`then`?不會被立即調用,而要等到當前?`job`?隊列執行到前述?`resolve`?過程才會被調用,然后其回調(也就是繼續?`await`?之后的語句)才加入?`job`?隊列,所以時序上就晚了。 ### **谷歌(金絲雀)73版本中** * 使用對`PromiseResolve`的調用來更改`await`的語義,以減少在公共`awaitPromise`情況下的轉換次數。 * 如果傳遞給?`await`?的值已經是一個?`Promise`,那么這種優化避免了再次創建?`Promise`?包裝器,在這種情況下,我們從最少三個?`microtick`?到只有一個?`microtick`。 ### **詳細過程:** **73以下版本** * 首先,打印`script start`,調用`async1()`時,返回一個`Promise`,所以打印出來`async2 end`。 * 每個?`await`,會新產生一個`promise`,但這個過程本身是異步的,所以該`await`后面不會立即調用。 * 繼續執行同步代碼,打印`Promise`和`script end`,將`then`函數放入**微任務**隊列中等待執行。 * 同步執行完成之后,檢查**微任務**隊列是否為`null`,然后按照先入先出規則,依次執行。 * 然后先執行打印`promise1`,此時`then`的回調函數返回`undefinde`,此時又有`then`的鏈式調用,又放入**微任務**隊列中,再次打印`promise2`。 * 再回到`await`的位置執行返回的?`Promise`?的?`resolve`?函數,這又會把?`resolve`?丟到微任務隊列中,打印`async1 end`。 * 當**微任務**隊列為空時,執行宏任務,打印`setTimeout`。 **谷歌(金絲雀73版本)** * 如果傳遞給?`await`?的值已經是一個?`Promise`,那么這種優化避免了再次創建?`Promise`?包裝器,在這種情況下,我們從最少三個?`microtick`?到只有一個?`microtick`。 * 引擎不再需要為?`await`?創造?`throwaway Promise`?- 在絕大部分時間。 * 現在?`promise`?指向了同一個?`Promise`,所以這個步驟什么也不需要做。然后引擎繼續像以前一樣,創建?`throwaway Promise`,安排?`PromiseReactionJob`?在?`microtask`?隊列的下一個?`tick`?上恢復異步函數,暫停執行該函數,然后返回給調用者。 具體詳情查看([這里](https://link.juejin.im/?target=https%3A%2F%2Fv8.js.cn%2Fblog%2Ffast-async%2F))。 ## NodeJS的Event Loop ![](https://cdn.nlark.com/yuque/0/2019/webp/128853/1560995436966-ae34b24c-83d0-4472-8854-1552abd9fcdb.webp#align=left&display=inline&height=223&originHeight=223&originWidth=543&size=0&status=done&width=543) `Node`中的`Event Loop`是基于`libuv`實現的,而`libuv`是?`Node`?的新跨平臺抽象層,libuv使用異步,事件驅動的編程方式,核心是提供`i/o`的事件循環和異步回調。libuv的`API`包含有時間,非阻塞的網絡,異步文件操作,子進程等等。?`Event Loop`就是在`libuv`中實現的。 ![](https://cdn.nlark.com/yuque/0/2019/webp/128853/1560995436946-e5bcfbd1-340e-4c68-a14e-cd000081eef4.webp#align=left&display=inline&height=442&originHeight=442&originWidth=745&size=0&status=done&width=745) ### `Node`的`Event loop`一共分為6個階段,每個細節具體如下: * `timers`: 執行`setTimeout`和`setInterval`中到期的`callback`。 * `pending callback`: 上一輪循環中少數的`callback`會放在這一階段執行。 * `idle, prepare`: 僅在內部使用。 * `poll`: 最重要的階段,執行`pending callback`,在適當的情況下回阻塞在這個階段。 * `check`: 執行`setImmediate`(`setImmediate()`是將事件插入到事件隊列尾部,主線程和事件隊列的函數執行完成之后立即執行`setImmediate`指定的回調函數)的`callback`。 * `close callbacks`: 執行`close`事件的`callback`,例如`socket.on('close'[,fn])`或者`http.server.on('close, fn)`。 具體細節如下: ### timers 執行`setTimeout`和`setInterval`中到期的`callback`,執行這兩者回調需要設置一個毫秒數,理論上來說,應該是時間一到就立即執行callback回調,但是由于`system`的調度可能會延時,達不到預期時間。 以下是官網文檔解釋的例子: ~~~ const fs = require('fs'); function someAsyncOperation(callback) { // Assume this takes 95ms to complete fs.readFile('/path/to/file', callback); } const timeoutScheduled = Date.now(); setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms have passed since I was scheduled`); }, 100); // do someAsyncOperation which takes 95 ms to complete someAsyncOperation(() => { const startCallback = Date.now(); // do something that will take 10ms... while (Date.now() - startCallback < 10) { // do nothing } }); ~~~ 當進入事件循環時,它有一個空隊列(`fs.readFile()`尚未完成),因此定時器將等待剩余毫秒數,當到達95ms時,`fs.readFile()`完成讀取文件并且其完成需要10毫秒的回調被添加到輪詢隊列并執行。 當回調結束時,隊列中不再有回調,因此事件循環將看到已達到最快定時器的**閾值**,然后回到**timers階段**以執行定時器的回調。 在此示例中,您將看到正在調度的計時器與正在執行的回調之間的總延遲將為105毫秒。 **以下是我測試時間:** ![](https://cdn.nlark.com/yuque/0/2019/webp/128853/1560995436982-2f817b92-3c4a-4d4c-8f95-92f63efa336c.webp#align=left&display=inline&height=430&originHeight=430&originWidth=724&size=0&status=done&width=724) ### pending callbacks 此階段執行某些系統操作(例如TCP錯誤類型)的回調。 例如,如果`TCP socket ECONNREFUSED`在嘗試connect時receives,則某些\* nix系統希望等待報告錯誤。 這將在`pending callbacks`階段執行。 ### poll **該poll階段有兩個主要功能:** * 執行`I/O`回調。 * 處理輪詢隊列中的事件。 **當事件循環進入`poll`階段并且在`timers`中沒有可以執行定時器時,將發生以下兩種情況之一** * 如果`poll`隊列不為空,則事件循環將遍歷其同步執行它們的`callback`隊列,直到隊列為空,或者達到`system-dependent`(系統相關限制)。 **如果`poll`隊列為空,則會發生以下兩種情況之一** * 如果有`setImmediate()`回調需要執行,則會立即停止執行`poll`階段并進入執行`check`階段以執行回調。 * 如果沒有`setImmediate()`回到需要執行,poll階段將等待`callback`被添加到隊列中,然后立即執行。 **當然設定了 timer 的話且 poll 隊列為空,則會判斷是否有 timer 超時,如果有的話會回到 timer 階段執行回調。** ### check **此階段允許人員在poll階段完成后立即執行回調。** 如果`poll`階段閑置并且`script`已排隊`setImmediate()`,則事件循環到達check階段執行而不是繼續等待。 `setImmediate()`實際上是一個特殊的計時器,它在事件循環的一個單獨階段運行。它使用`libuv API`來調度在`poll`階段完成后執行的回調。 通常,當代碼被執行時,事件循環最終將達到`poll`階段,它將等待傳入連接,請求等。 但是,如果已經調度了回調`setImmediate()`,并且輪詢階段變為空閑,則它將結束并且到達`check`階段,而不是等待`poll`事件。 ~~~ console.log('start') setTimeout(() => { console.log('timer1') Promise.resolve().then(function() { console.log('promise1') }) }, 0) setTimeout(() => { console.log('timer2') Promise.resolve().then(function() { console.log('promise2') }) }, 0) Promise.resolve().then(function() { console.log('promise3') }) console.log('end') ~~~ 如果`node`版本為`v11.x`, 其結果與瀏覽器一致。 ~~~ start end promise3 timer1 promise1 timer2 promise2 ~~~ 具體詳情可以查看《[又被node的eventloop坑了,這次是node的鍋](https://juejin.im/post/5c3e8d90f265da614274218a)》。 如果v10版本上述結果存在兩種情況: * 如果time2定時器已經在執行隊列中了 ~~~ start end promise3 timer1 timer2 promise1 promise2 ~~~ * 如果time2定時器沒有在執行對列中,執行結果為 ~~~ start end promise3 timer1 promise1 timer2 promise2 ~~~ 具體情況可以參考`poll`階段的兩種情況。 從下圖可能更好理解: ![](https://cdn.nlark.com/yuque/0/2019/gif/128853/1560995436960-165cb65c-477f-4b4c-8a0a-79d3136f342e.gif#align=left&display=inline&height=333&originHeight=333&originWidth=598&size=0&status=done&width=598) ## setImmediate() 的setTimeout()的區別 **`setImmediate`和`setTimeout()`是相似的,但根據它們被調用的時間以不同的方式表現。** * `setImmediate()`設計用于在當前`poll`階段完成后check階段執行腳本 。 * `setTimeout()`?安排在經過最小(ms)后運行的腳本,在`timers`階段執行。 ### 舉個例子 ~~~ setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); ~~~ **執行定時器的順序將根據調用它們的上下文而有所不同。 如果從主模塊中調用兩者,那么時間將受到進程性能的限制。** **其結果也不一致** **如果在`I / O`周期內移動兩個調用,則始終首先執行立即回調:** ~~~ const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); }); ~~~ 其結果可以確定一定是`immediate => timeout`。 主要原因是在`I/O階段`讀取文件后,事件循環會先進入`poll`階段,發現有`setImmediate`需要執行,會立即進入`check`階段執行`setImmediate`的回調。 然后再進入`timers`階段,執行`setTimeout`,打印`timeout`。 ~~~ ┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘ ~~~ ## Process.nextTick() **`process.nextTick()`雖然它是異步API的一部分,但未在圖中顯示。這是因為`process.nextTick()`從技術上講,它不是事件循環的一部分。** * `process.nextTick()`方法將?`callback`?添加到`next tick`隊列。 一旦當前事件輪詢隊列的任務全部完成,在`next tick`隊列中的所有`callbacks`會被依次調用。 **換種理解方式:** * 當每個階段完成后,如果存在?`nextTick`?隊列,就會清空隊列中的所有回調函數,并且優先于其他?`microtask`?執行。 ### 例子 ~~~ let bar; setTimeout(() => { console.log('setTimeout'); }, 0) setImmediate(() => { console.log('setImmediate'); }) function someAsyncApiCall(callback) { process.nextTick(callback); } someAsyncApiCall(() => { console.log('bar', bar); // 1 }); bar = 1; ~~~ 在NodeV10中上述代碼執行可能有兩種答案,一種為: ~~~ bar 1 setTimeout setImmediate ~~~ 另一種為: ~~~ bar 1 setImmediate setTimeout ~~~ 無論哪種,始終都是先執行`process.nextTick(callback)`,打印`bar 1`。 * * * ## 公眾號 想要實時關注筆者最新的文章和最新的文檔更新請關注公眾號**程序員面試官**,后續的文章會優先在公眾號更新. **簡歷模板**:關注公眾號回復「模板」獲取 《**前端面試手冊**》:配套于本指南的突擊手冊,關注公眾號回復「fed」獲取 ![2019-08-12-03-18-41](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/d846f65d5025c4b6c4619662a0669503.png)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看