<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>

          一文徹底吃透 JavaScript 的執(zhí)行機制

          共 3733字,需瀏覽 8分鐘

           ·

          2020-11-27 11:52

          執(zhí)行 & 運行

          首先我們需要聲明下,JavaScript?的執(zhí)行和運行是兩個不同概念的,執(zhí)行,一般依賴于環(huán)境,比如?node、瀏覽器、Ringo?等,?JavaScript 在不同環(huán)境下的執(zhí)行機制可能并不相同。而今天我們要討論的?Event Loop?就是?JavaScript?的一種執(zhí)行方式。所以下文我們還會梳理?node?的執(zhí)行方式。而運行呢,是指JavaScript 的解析引擎。這是統一的。

          關于 JavaScript

          此篇文章中,這個小標題下,我們只需要牢記一句話:?JavaScript 是單線程語言?,無論HTML5?里面?Web-Worker?還是 node 里面的cluster都是“紙老虎”,而且?cluster?還是進程管理相關。這里讀者注意區(qū)分:進程和線程。

          既然?JavaScript?是單線程語言,那么就會存在一個問題,所有的代碼都得一句一句的來執(zhí)行。就像我們在食堂排隊打飯,必須一個一個排隊點菜結賬。那些沒有排到的,就得等著~

          概念梳理

          在詳解執(zhí)行機制之前,先梳理一下?JavaScript?的一些基本概念,方便后面我們說到的時候大伙兒心里有個印象和大概的輪廓。

          事件循環(huán)(Event Loop)

          什么是 Event Loop?

          其實這個概念還是比較模糊的,因為他必須得結合著運行機制來解釋。

          JavaScript?有一個主線程?main thread,和調用棧?call-stack?也稱之為執(zhí)行棧。所有的任務都會放到調用棧中等待主線程來執(zhí)行。

          暫且,我們先理解為上圖的大圈圈就是 Event Loop 吧!并且,這個圈圈,一直在轉圈圈~ 也就是說,JavaScript?的?Event Loop?是伴隨著整個源碼文件生命周期的,只要當前?JavaScript?在運行中,內部的這個循環(huán)就會不斷地循環(huán)下去,去尋找?queue?里面能執(zhí)行的?task

          任務隊列(task queue)

          task,就是任務的意思,我們這里理解為每一個語句就是一個任務

          console.log(1);
          console.log(2);

          如上語句,其實就是就可以理解為兩個?task

          而?queue?呢,就是FIFO的隊列!

          所以?Task Queue?就是承載任務的隊列。而?JavaScript?的?Event Loop就是會不斷地過來找這個?queue,問有沒有?task?可以運行運行。

          同步任務(SyncTask)、異步任務(AsyncTask)

          同步任務說白了就是主線程來執(zhí)行的時候立即就能執(zhí)行的代碼,比如:

          console.log('this is THE LAST TIME');
          console.log('Nealyang');

          代碼在執(zhí)行到上述?console?的時候,就會立即在控制臺上打印相應結果。

          而所謂的異步任務就是主線程執(zhí)行到這個?task?的時候,“唉!你等會,我現在先不執(zhí)行,等我 xxx 完了以后我再來等你執(zhí)行” 注意上述我說的是等你來執(zhí)行。

          說白了,異步任務就是你先去執(zhí)行別的 task,等我這 xxx 完之后再往 Task Queue 里面塞一個 task 的同步任務來等待被執(zhí)行

          setTimeout(()=>{
          console.log(2)
          });
          console.log(1);

          如上述代碼,setTimeout?就是一個異步任務,主線程去執(zhí)行的時候遇到setTimeout?發(fā)現是一個異步任務,就先注冊了一個異步的回調,然后接著執(zhí)行下面的語句console.log(1),等上面的異步任務等待的時間到了以后,在執(zhí)行console.log(2)。具體的執(zhí)行機制會在后面剖析。

          • 主線程自上而下執(zhí)行所有代碼

          • 同步任務直接進入到主線程被執(zhí)行,而異步任務則進入到?Event Table?并注冊相對應的回調函數

          • 異步任務完成后,Event Table?會將這個函數移入?Event Queue

          • 主線程任務執(zhí)行完了以后,會從Event Queue中讀取任務,進入到主線程去執(zhí)行。

          • 循環(huán)如上

          上述動作不斷循環(huán),就是我們所說的事件循環(huán)(Event Loop)。

          小試牛刀

          ajax({
          url:www.Nealyang.com,
          data:prams,
          success:() => {
          console.log('請求成功!');
          },
          error:()=>{
          console.log('請求失敗~');
          }
          })
          console.log('這是一個同步任務');
          • ajax 請求首先進入到?Event Table?,分別注冊了onErroronSuccess回調函數。

          • 主線程執(zhí)行同步任務:console.log('這是一個同步任務');

          • 主線程任務執(zhí)行完畢,看Event Queue是否有待執(zhí)行的 task,這里是不斷地檢查,只要主線程的task queue沒有任務執(zhí)行了,主線程就一直在這等著

          • ajax 執(zhí)行完畢,將回調函數push?到Event Queue。(步驟 3、4 沒有先后順序而言)

          • 主線程“終于”等到了Event Queue里有?task可以執(zhí)行了,執(zhí)行對應的回調任務。

          • 如此往復。

          宏任務(MacroTask)、微任務(MicroTask)

          JavaScript?的任務不僅僅分為同步任務和異步任務,同時從另一個維度,也分為了宏任務(MacroTask)和微任務(MicroTask)。

          先說說?MacroTask,所有的同步任務代碼都是MacroTask(這么說其實不是很嚴謹,下面解釋),setTimeoutsetIntervalI/OUI Rendering?等都是宏任務。

          MicroTask,為什么說上述不嚴謹我卻還是強調所有的同步任務都是?MacroTask?呢,因為我們僅僅需要記住幾個?MicroTask?即可,排除法!別的都是?MacroTaskMicroTask?包括:Process.nextTickPromise.then catch finally(注意我不是說 Promise)、MutationObserver

          瀏覽器環(huán)境下的 Event Loop

          當我們梳理完哪些是?MicroTask?,除了那些別的都是?MacroTask?后,哪些是同步任務,哪些又是異步任務后,這里就應該徹底的梳理下JavaScript 的執(zhí)行機制了。

          如開篇說到的,執(zhí)行和運行是不同的,執(zhí)行要區(qū)分環(huán)境。所以這里我們將Event Loop?的介紹分為瀏覽器和 Node 兩個環(huán)境下。

          先放圖鎮(zhèn)樓!如果你已經理解了這張圖的意思,那么恭喜你,你完全可以直接閱讀 Node 環(huán)境下的?Event Loop?章節(jié)了!

          setTimeout、setInterval

          setTimeout

          setTimeout?就是等多長時間來執(zhí)行這個回調函數。setInterval?就是每隔多長時間來執(zhí)行這個回調。

          let startTime = new Date().getTime();

          setTimeout(()=>{
          console.log(new Date().getTime()-startTime);
          },1000);

          如上代碼,顧名思義,就是等 1s 后再去執(zhí)行?console。放到瀏覽器下去執(zhí)行,OK,如你所愿就是如此。

          但是這次我們在探討 JavaScript 的執(zhí)行機制,所以這里我們得探討下如下代碼:

          let startTime = new Date().getTime();

          console.log({startTime})

          setTimeout(()=>{
          console.log(`開始執(zhí)行回調的相隔時差:${new Date().getTime()-startTime}`);
          },1000);

          for(let i = 0;i<40000;i++){
          console.log(1)
          }

          如上運行,setTimeout?的回調函數等到 4.7s 以后才執(zhí)行!而這時候,我們把?setTimeout?的 1s 延遲給刪了:

          let startTime = new Date().getTime();

          console.log({startTime})

          setTimeout(()=>{
          console.log(`開始執(zhí)行回調的相隔時差:${new Date().getTime()-startTime}`);
          },0);

          for(let i = 0;i<40000;i++){
          console.log(1)
          }

          結果依然是等到 4.7s 后才執(zhí)行setTimeout 的回調。貌似 setTimeout 后面的延遲并沒有產生任何效果!

          其實這么說,又應該回到上面的那張 JavaScript 執(zhí)行的流程圖了。

          setTimeout這里就是簡單的異步,我們通過上面的圖來分析上述代碼的一步一步執(zhí)行情況

          • 首先?JavaScript?自上而下執(zhí)行代碼

          • 遇到遇到賦值語句、以及第一個?console.log({startTime})?分別作為一個?task,壓入到立即執(zhí)行棧中被執(zhí)行。

          • 遇到?setTImeout?是一個異步任務,則注冊相應回調函數。(異步函數告訴你,js 你先別急,等 1s 后我再將回調函數:console.log(xxx)放到?Task Queue?中)

          • OK,這時候 JavaScript 則接著往下走,遇到了 40000 個 for 循環(huán)的 task,沒辦法,1s 后都還沒執(zhí)行完。其實這個時候上述的回調已經在Task Queue?中了。

          • 等所有的立即執(zhí)行棧中的 task 都執(zhí)行完了,在回頭看?Task Queue?中的任務,發(fā)現異步的回調 task 已經在里面了,所以接著執(zhí)行。

          打個比方

          其實上述的不僅僅是 timeout,而是任何異步,比如網絡請求等。

          就好比,我六點鐘下班了,可以安排下自己的活動了!

          然后收拾電腦(同步任務)、收拾書包(同步任務)、給女朋友打電話說出來吃飯吧(必然是異步任務),然后女朋友說你等會,我先化個妝,等我畫好了call你。

          那我不能干等著呀,就接著做別的事情,比如那我就在改個 bug 吧,你好了通知我。結果等她一個小時后說我化好妝了,我們出去吃飯吧。不行!我 bug 還沒有解決掉呢?你等會。。。。其實這個時候你的一小時化妝還是 5 分鐘化妝都已經毫無意義了。。。因為哥哥這會沒空~~

          如果我 bug 在半個小時就解決完了,沒別的任務需要執(zhí)行了,那么就在這等著呀!必須等著!隨時待命!。然后女朋友來電話了,我化完妝了,我們出去吃飯吧,那么剛好,我們在你的完成了請求或者 timeout 時間到了后我剛好閑著,那么我必須立即執(zhí)行了。

          setInterval

          說完了?setTimeout,當然不能錯過他的孿生兄弟:setInterval。對于執(zhí)行順序來說,setInterval會每隔指定的時間將注冊的函數置入?Task Queue,如果前面的任務耗時太久,那么同樣需要等待。

          這里需要說的是,對于?setInterval(fn,ms)?來說,我們制定沒?xx ms執(zhí)行一次?fn,其實是沒?xx ms,會有一個fn?進入到?Task Queue?中。一旦 setInterval 的回調函數fn執(zhí)行時間超過了xx ms,那么就完全看不出來有時間間隔了。?仔細回味回味,是不是那么回事?

          Promise

          關于?Promise?的用法,這里就不過過多介紹了,后面會在寫《【THE LAST TIME】徹底吃透 JavaScript 異步》 一文的時候詳細介紹。這里我們只說 JavaScript 的執(zhí)行機制。

          如上所說,promise.then?、catch?和?finally?是屬于?MicroTask。這里主要是異步的區(qū)分。展開說明之前,我們結合上述說的,再來“扭曲”梳理一下。

          為了避免初學者這時候腦子有點混亂,我們暫時忘掉 JavaScript 異步任務!?我們暫且稱之為待會再執(zhí)行的同步任務。

          有了如上約束后,我們可以說,JavaScript 從一開始就自上而下的執(zhí)行每一個語句(Task),這時候只能遇到立馬就要執(zhí)行的任務和待會再執(zhí)行的任務。對于那待會再執(zhí)行的任務等到能執(zhí)行了,也不會立即執(zhí)行,你得等js 執(zhí)行完這一趟才行

          再打個比方

          就像做公交車一樣,公交車不等人呀,公交車路線上有人就會停(農村公交!么得站牌),但是等公交車來,你跟司機說,我肚子疼要拉x~這時候公交不會等你。你只能拉完以后等公交下一趟再來(大山里!一個路線就一趟車)。

          OK!你拉完了。。。等公交,公交也很快到了!但是,你不能立馬上車,因為這時候前面有個孕婦!有個老人!還有熊孩子,你必須得讓他們先上車,然后你才能上車!

          而這些 孕婦、老人、熊孩子所組成的就是傳說中的?MicroTask Queue,而且,就在你和你的同事、朋友就必須在他們后面上車。

          這里我們沒有異步的概念,只有同樣的一次循環(huán)回來,有了兩種隊伍,一種優(yōu)先上車的隊伍叫做MicroTask Queue,而你和你的同事這幫壯漢組成的隊伍就是宏隊伍(MacroTask Queue)。

          一句話理解:一次事件循環(huán)回來后,開始去執(zhí)行?Task Queue?中的task,但是這里的?task?有優(yōu)先級。所以優(yōu)先執(zhí)行?MicroTask Queue?中的 task,執(zhí)行完后在執(zhí)行MacroTask Queue?中的 task

          小試牛刀

          理論都扯完了,也不知道你懂沒懂。來,期中考試了!

          console.log('script start');

          setTimeout(function() {
          console.log('setTimeout');
          }, 0);

          Promise.resolve().then(function() {
          console.log('promise1');
          }).then(function() {
          console.log('promise2');
          });

          沒必要搞個 setTimeout 有加個 Promise,Promise 里面再整個 setTimeout 的例子。因為只要上面代碼你懂了,無非就是公交再來一趟而已!

          如果說了這么多,還是沒能理解上圖,那么公眾號內回復【1】,手摸手指導!

          Node 環(huán)境下的 Event Loop

          Node中的Event Loop是基于libuv實現的,而libuv是 Node 的新跨平臺抽象層,libuv使用異步,事件驅動的編程方式,核心是提供i/o的事件循環(huán)和異步回調。libuvAPI包含有時間,非阻塞的網絡,異步文件操作,子進程等等。

          Event Loop就是在libuv中實現的。所以關于 Node 的?Event Loop學習,有兩個官方途徑可以學習:

          • libuv 文檔

          • 官網的What is the Event Loop?.

          在學習 Node 環(huán)境下的?Event Loop?之前呢,我們首先要明確執(zhí)行環(huán)境,Node 和瀏覽器的Event Loop是兩個有明確區(qū)分的事物,不能混為一談。nodejs的event是基于libuv,而瀏覽器的event loop則在html5的規(guī)范中明確定義。

             ┌───────────────────────────┐
          ┌─>│ timers │
          │ └─────────────┬─────────────┘
          │ ┌─────────────┴─────────────┐
          │ │ pending callbacks │
          │ └─────────────┬─────────────┘
          │ ┌─────────────┴─────────────┐
          │ │ idle, prepare │
          │ └─────────────┬─────────────┘ ┌───────────────┐
          │ ┌─────────────┴─────────────┐ │ incoming: │
          │ │ poll │<─────┤ connections, │
          │ └─────────────┬─────────────┘ │ data, etc. │
          │ ┌─────────────┴─────────────┐ └───────────────┘
          │ │ check │
          │ └─────────────┬─────────────┘
          │ ┌─────────────┴─────────────┐
          └──┤ close callbacks │
          └───────────────────────────┘

          Node 的 Event Loop 分為 6 個階段:

          • timers:執(zhí)行setTimeout()?和?setInterval()中到期的callback。

          • pending callback: 上一輪循環(huán)中有少數的I/O?callback會被延遲到這一輪的這一階段執(zhí)行

          • idle, prepare:僅內部使用

          • poll: 最為重要的階段,執(zhí)行I/O?callback,在適當的條件下會阻塞在這個階段

          • check: 執(zhí)行setImmediate的callback

          • close callbacks: 執(zhí)行close事件的callback,例如socket.on('close'[,fn])http.server.on('close, fn)

          上面六個階段都不包括 process.nextTick()(下文會介紹)

          整體的執(zhí)行機制如上圖所示,下面我們具體展開每一個階段的說明

          timers 階段

          timers 階段會執(zhí)行?setTimeout?和?setInterval?回調,并且是由 poll 階段控制的。

          在 timers 階段其實使用一個最小堆而不是隊列來保存所有的元素,其實也可以理解,因為timeout的callback是按照超時時間的順序來調用的,并不是先進先出的隊列邏輯)。而為什么 timer 階段在第一個執(zhí)行階梯上其實也不難理解。在 Node 中定時器指定的時間也是不準確的,而這樣,就能盡可能的準確了,讓其回調函數盡快執(zhí)行。

          以下是官網給出的例子:

          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
          }
          });

          當進入事件循環(huán)時,它有一個空隊列(fs.readFile()尚未完成),因此定時器將等待剩余毫秒數,當到達95ms時,fs.readFile()完成讀取文件并且其完成需要10毫秒的回調被添加到輪詢隊列并執(zhí)行。

          當回調結束時,隊列中不再有回調,因此事件循環(huán)將看到已達到最快定時器的閾值,然后回到timers階段以執(zhí)行定時器的回調。在此示例中,您將看到正在調度的計時器與正在執(zhí)行的回調之間的總延遲將為105毫秒。

          pending callbacks 階段

          pending callbacks 階段其實是?I/O?的 callbacks 階段。比如一些 TCP 的 error 回調等。

          舉個栗子:如果TCP socket ECONNREFUSED在嘗試connectreceives,則某些* nix系統希望等待報告錯誤。這將在pending callbacks階段執(zhí)行。

          poll 階段

          poll 階段主要有兩個功能:

          • 執(zhí)行?I/O?回調

          • 處理 poll 隊列(poll queue)中的事件

          當時Event Loop 進入到 poll 階段并且 timers 階段沒有任何可執(zhí)行的 task 的時候(也就是沒有定時器回調),將會有以下兩種情況

          • 如果 poll queue 非空,則 Event Loop就會執(zhí)行他們,知道為空或者達到system-dependent(系統相關限制)

          • 如果 poll queue 為空,則會發(fā)生以下一種情況

            • 如果setImmediate()有回調需要執(zhí)行,則會立即進入到 check 階段

            • 相反,如果沒有setImmediate()需要執(zhí)行,則 poll 階段將等待 callback 被添加到隊列中再立即執(zhí)行,這也是為什么我們說 poll 階段可能會阻塞的原因。

          一旦 poll queue 為空,Event Loop就回去檢查timer 階段的任務。如果有的話,則會回到 timer 階段執(zhí)行回調。

          check 階段

          check 階段在 poll 階段之后,setImmediate()的回調會被加入check隊列中,他是一個使用libuv API?的特殊的計數器。

          通常在代碼執(zhí)行的時候,Event Loop 最終會到達 poll 階段,然后等待傳入的鏈接或者請求等,但是如果已經指定了setImmediate()并且這時候 poll 階段已經空閑的時候,則 poll 階段將會被中止然后開始 check 階段的執(zhí)行。

          close callbacks 階段

          如果一個 socket 或者事件處理函數突然關閉/中斷(比如:socket.destroy()),則這個階段就會發(fā)生?close?的回調執(zhí)行。否則他會通過?process.nextTick()?發(fā)出。

          setImmediate() vs setTimeout()

          setImmediate()?和?setTimeout()非常的相似,區(qū)別取決于誰調用了它。

          • setImmediate在 poll 階段后執(zhí)行,即check 階段

          • setTimeout?在 poll 空閑時且設定時間到達的時候執(zhí)行,在 timer 階段

          計時器的執(zhí)行順序將根據調用它們的上下文而有所不同。如果兩者都是從主模塊中調用的,則時序將受到進程性能的限制。

          例如,如果我們運行以下不在I / O周期(即主模塊)內的腳本,則兩個計時器的執(zhí)行順序是不確定的,因為它受進程性能的約束:

          // timeout_vs_immediate.js
          setTimeout(() => {
          console.log('timeout');
          }, 0);

          setImmediate(() => {
          console.log('immediate');
          });
          $ node timeout_vs_immediate.js
          timeout
          immediate

          $ node timeout_vs_immediate.js
          immediate
          timeout

          如果在一個I/O?周期內移動這兩個調用,則始終首先執(zhí)行立即回調:

          // timeout_vs_immediate.js
          const fs = require('fs');

          fs.readFile(__filename, () => {
          setTimeout(() => {
          console.log('timeout');
          }, 0);
          setImmediate(() => {
          console.log('immediate');
          });
          });
          $ node timeout_vs_immediate.js
          immediate
          timeout

          $ node timeout_vs_immediate.js
          immediate
          timeout

          所以與setTimeout()相比,使用setImmediate()的主要優(yōu)點是,如果在I / O周期內安排了任何計時器,則setImmediate()將始終在任何計時器之前執(zhí)行,而與存在多少計時器無關。

          nextTick queue

          可能你已經注意到process.nextTick()并未顯示在圖中,即使它是異步API的一部分。所以他擁有一個自己的隊列:nextTickQueue

          這是因為process.nextTick()從技術上講不是Event Loop的一部分。相反,無論當前事件循環(huán)的當前階段如何,都將在當前操作完成之后處理nextTickQueue

          如果存在?nextTickQueue,就會清空隊列中的所有回調函數,并且優(yōu)先于其他?microtask?執(zhí)行。

          setTimeout(() => {
          console.log('timer1')
          Promise.resolve().then(function() {
          console.log('promise1')
          })
          }, 0)
          process.nextTick(() => {
          console.log('nextTick')
          process.nextTick(() => {
          console.log('nextTick')
          process.nextTick(() => {
          console.log('nextTick')
          process.nextTick(() => {
          console.log('nextTick')
          })
          })
          })
          })
          // nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1

          process.nextTick() vs setImmediate()

          從使用者角度而言,這兩個名稱非常的容易讓人感覺到困惑。

          • process.nextTick()在同一階段立即觸發(fā)

          • setImmediate()在事件循環(huán)的以下迭代或“tick”中觸發(fā)

          貌似這兩個名稱應該互換下!的確~官方也這么認為。但是他們說這是歷史包袱,已經不會更改了。

          這里還是建議大家盡可能使用setImmediate。因為更加的讓程序可控容易推理。

          至于為什么還是需要?process.nextTick,存在即合理。這里建議大家閱讀官方文檔:why-use-process-nexttick。

          Node與瀏覽器的 Event Loop 差異

          一句話總結其中:瀏覽器環(huán)境下,microtask的任務隊列是每個macrotask執(zhí)行完之后執(zhí)行。而在Node.js中,microtask會在事件循環(huán)的各個階段之間執(zhí)行,也就是一個階段執(zhí)行完畢,就會去執(zhí)行microtask隊列的任務。

          上圖來自浪里行舟

          最后

          來~期末考試了

          console.log('1');

          setTimeout(function() {
          console.log('2');
          process.nextTick(function() {
          console.log('3');
          })
          new Promise(function(resolve) {
          console.log('4');
          resolve();
          }).then(function() {
          console.log('5')
          })
          })
          process.nextTick(function() {
          console.log('6');
          })
          new Promise(function(resolve) {
          console.log('7');
          resolve();
          }).then(function() {
          console.log('8')
          })

          setTimeout(function() {
          console.log('9');
          process.nextTick(function() {
          console.log('10');
          })
          new Promise(function(resolve) {
          console.log('11');
          resolve();
          }).then(function() {
          console.log('12')
          })
          })

          評論區(qū)留下你的答案吧~~老鐵!

          參考文獻

          • Tasks, microtasks, queues and schedules

          • libuv 文檔

          • The Node.js Event Loop, Timers, and process.nextTick()

          • node 官網

          • async/await 在chrome 環(huán)境和 node 環(huán)境的 執(zhí)行結果不一致,求解?

          • 更快的異步函數和 Promise

          • 一次弄懂Event Loop(徹底解決此類面試問題)

          • 這一次,徹底弄懂 JavaScript 執(zhí)行機制

          • 不要混淆nodejs和瀏覽器中的event loop




          瀏覽 42
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产一级a毛一级a做视频 | 黄色电影网站日韩 | 天天干天天操青青草 | 无码一区二区三区嫩草网你懂的 | 一区二区做爱视频 |