国产精品美女久久久浪潮AV,国产精品三级一二三区,久久精品国产一区二区小说 ,依依成人影视国产精品,全部无卡免费的毛片在线看,日本一区二区三深夜不卡,国产精品女同一区二区久久,国产精品夜色一区二区三区

        JavaScript中的Event Loop(事件循環(huán))機制

        2020-5-25    seo達人

        事件循環(huán)

        JavaScript是單線程,非阻塞的

        瀏覽器的事件循環(huán)


        執(zhí)行棧和事件隊列

        宏任務和微任務

        node環(huán)境下的事件循環(huán)


        和瀏覽器環(huán)境有何不同

        事件循環(huán)模型

        宏任務和微任務

        經(jīng)典題目分析

        1. JavaScript是單線程,非阻塞的

        單線程:


        JavaScript的主要用途是與用戶互動,以及操作DOM。如果它是多線程的會有很多復雜的問題要處理,比如有兩個線程同時操作DOM,一個線程刪除了當前的DOM節(jié)點,一個線程是要操作當前的DOM階段,最后以哪個線程的操作為準?為了避免這種,所以JS是單線程的。即使H5提出了web worker標準,它有很多限制,受主線程控制,是主線程的子線程。


        非阻塞:通過 event loop 實現(xiàn)。


        2. 瀏覽器的事件循環(huán)

        執(zhí)行棧和事件隊列

        為了更好地理解Event Loop,請看下圖(轉引自Philip Roberts的演講 《Help, I'm stuck in an event-loop》)

        Help, I'm stuck in an event-loop


        執(zhí)行棧: 同步代碼的執(zhí)行,按照順序添加到執(zhí)行棧中


        function a() {

           b();

           console.log('a');

        }

        function b() {

           console.log('b')

        }

        a();

        我們可以通過使用 Loupe(Loupe是一種可視化工具,可以幫助您了解JavaScript的調用堆棧/事件循環(huán)/回調隊列如何相互影響)工具來了解上面代碼的執(zhí)行情況。


        調用情況


        執(zhí)行函數(shù) a()先入棧

        a()中先執(zhí)行函數(shù) b() 函數(shù)b() 入棧

        執(zhí)行函數(shù)b(), console.log('b') 入棧

        輸出 b, console.log('b')出棧

        函數(shù)b() 執(zhí)行完成,出棧

        console.log('a') 入棧,執(zhí)行,輸出 a, 出棧

        函數(shù)a 執(zhí)行完成,出棧。

        事件隊列: 異步代碼的執(zhí)行,遇到異步事件不會等待它返回結果,而是將這個事件掛起,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務。當異步事件返回結果,將它放到事件隊列中,被放入事件隊列不會立刻執(zhí)行起回調,而是等待當前執(zhí)行棧中所有任務都執(zhí)行完畢,主線程空閑狀態(tài),主線程會去查找事件隊列中是否有任務,如果有,則取出排在第一位的事件,并把這個事件對應的回調放到執(zhí)行棧中,然后執(zhí)行其中的同步代碼。


        我們再上面代碼的基礎上添加異步事件,


        function a() {

           b();

           console.log('a');

        }

        function b() {

           console.log('b')

           setTimeout(function() {

               console.log('c');

           }, 2000)

        }

        a();

        此時的執(zhí)行過程如下

        img


        我們同時再加上點擊事件看一下運行的過程


        $.on('button', 'click', function onClick() {

           setTimeout(function timer() {

               console.log('You clicked the button!');    

           }, 2000);

        });


        console.log("Hi!");


        setTimeout(function timeout() {

           console.log("Click the button!");

        }, 5000);


        console.log("Welcome to loupe.");

        img


        簡單用下面的圖進行一下總結


        執(zhí)行棧和事件隊列


        宏任務和微任務

        為什么要引入微任務,只有一種類型的任務不行么?


        頁面渲染事件,各種IO的完成事件等隨時被添加到任務隊列中,一直會保持先進先出的原則執(zhí)行,我們不能準確地控制這些事件被添加到任務隊列中的位置。但是這個時候突然有高優(yōu)先級的任務需要盡快執(zhí)行,那么一種類型的任務就不合適了,所以引入了微任務隊列。


        不同的異步任務被分為:宏任務和微任務

        宏任務:


        script(整體代碼)

        setTimeout()

        setInterval()

        postMessage

        I/O

        UI交互事件

        微任務:


        new Promise().then(回調)

        MutationObserver(html5 新特性)

        運行機制

        異步任務的返回結果會被放到一個任務隊列中,根據(jù)異步事件的類型,這個事件實際上會被放到對應的宏任務和微任務隊列中去。


        在當前執(zhí)行棧為空時,主線程會查看微任務隊列是否有事件存在


        存在,依次執(zhí)行隊列中的事件對應的回調,直到微任務隊列為空,然后去宏任務隊列中取出最前面的事件,把當前的回調加到當前指向棧。

        如果不存在,那么再去宏任務隊列中取出一個事件并把對應的回到加入當前執(zhí)行棧;

        當前執(zhí)行棧執(zhí)行完畢后時會立刻處理所有微任務隊列中的事件,然后再去宏任務隊列中取出一個事件。同一次事件循環(huán)中,微任務永遠在宏任務之前執(zhí)行。


        在事件循環(huán)中,每進行一次循環(huán)操作稱為 tick,每一次 tick 的任務處理模型是比較復雜的,但關鍵步驟如下:


        執(zhí)行一個宏任務(棧中沒有就從事件隊列中獲取)

        執(zhí)行過程中如果遇到微任務,就將它添加到微任務的任務隊列中

        宏任務執(zhí)行完畢后,立即執(zhí)行當前微任務隊列中的所有微任務(依次執(zhí)行)

        當前宏任務執(zhí)行完畢,開始檢查渲染,然后GUI線程接管渲染

        渲染完畢后,JS線程繼續(xù)接管,開始下一個宏任務(從事件隊列中獲取)

        簡單總結一下執(zhí)行的順序:

        執(zhí)行宏任務,然后執(zhí)行該宏任務產(chǎn)生的微任務,若微任務在執(zhí)行過程中產(chǎn)生了新的微任務,則繼續(xù)執(zhí)行微任務,微任務執(zhí)行完畢后,再回到宏任務中進行下一輪循環(huán)。


        宏任務和微任務


        深入理解js事件循環(huán)機制(瀏覽器篇) 這邊文章中有個特別形象的動畫,大家可以看著理解一下。


        console.log('start')


        setTimeout(function() {

         console.log('setTimeout')

        }, 0)


        Promise.resolve().then(function() {

         console.log('promise1')

        }).then(function() {

         console.log('promise2')

        })


        console.log('end')

        瀏覽器事件循環(huán)


        全局代碼壓入執(zhí)行棧執(zhí)行,輸出 start

        setTimeout壓入 macrotask隊列,promise.then 回調放入 microtask隊列,最后執(zhí)行 console.log('end'),輸出 end

        調用棧中的代碼執(zhí)行完成(全局代碼屬于宏任務),接下來開始執(zhí)行微任務隊列中的代碼,執(zhí)行promise回調,輸出 promise1, promise回調函數(shù)默認返回 undefined, promise狀態(tài)變成 fulfilled ,觸發(fā)接下來的 then回調,繼續(xù)壓入 microtask隊列,此時產(chǎn)生了新的微任務,會接著把當前的微任務隊列執(zhí)行完,此時執(zhí)行第二個 promise.then回調,輸出 promise2

        此時,microtask隊列 已清空,接下來會會執(zhí)行 UI渲染工作(如果有的話),然后開始下一輪 event loop, 執(zhí)行 setTimeout的回調,輸出 setTimeout

        最后的執(zhí)行結果如下


        start

        end

        promise1

        promise2

        setTimeout

        node環(huán)境下的事件循環(huán)

        和瀏覽器環(huán)境有何不同

        表現(xiàn)出的狀態(tài)與瀏覽器大致相同。不同的是 node 中有一套自己的模型。node 中事件循環(huán)的實現(xiàn)依賴 libuv 引擎。Node的事件循環(huán)存在幾個階段。


        如果是node10及其之前版本,microtask會在事件循環(huán)的各個階段之間執(zhí)行,也就是一個階段執(zhí)行完畢,就會去執(zhí)行 microtask隊列中的任務。


        node版本更新到11之后,Event Loop運行原理發(fā)生了變化,一旦執(zhí)行一個階段里的一個宏任務(setTimeout,setInterval和setImmediate)就立刻執(zhí)行微任務隊列,跟瀏覽器趨于一致。下面例子中的代碼是按照的去進行分析的。


        事件循環(huán)模型

        ┌───────────────────────┐

        ┌─>│        timers         │

        │  └──────────┬────────────┘

        │  ┌──────────┴────────────┐

        │  │     I/O callbacks     │

        │  └──────────┬────────────┘

        │  ┌──────────┴────────────┐

        │  │     idle, prepare     │

        │  └──────────┬────────────┘      ┌───────────────┐

        │  ┌──────────┴────────────┐      │   incoming:   │

        │  │         poll          │<──connections───     │

        │  └──────────┬────────────┘      │   data, etc.  │

        │  ┌──────────┴────────────┐      └───────────────┘

        │  │        check          │

        │  └──────────┬────────────┘

        │  ┌──────────┴────────────┐

        └──┤    close callbacks    │

          └───────────────────────┘

        事件循環(huán)各階段詳解

        node中事件循環(huán)的順序


        外部輸入數(shù)據(jù) --> 輪詢階段(poll) --> 檢查階段(check) --> 關閉事件回調階段(close callback) --> 定時器檢查階段(timer) --> I/O 事件回調階段(I/O callbacks) --> 閑置階段(idle, prepare) --> 輪詢階段...


        這些階段大致的功能如下:


        定時器檢測階段(timers): 這個階段執(zhí)行定時器隊列中的回調如 setTimeout() 和 setInterval()。

        I/O事件回調階段(I/O callbacks): 這個階段執(zhí)行幾乎所有的回調。但是不包括close事件,定時器和setImmediate()的回調。

        閑置階段(idle, prepare): 這個階段僅在內(nèi)部使用,可以不必理會

        輪詢階段(poll): 等待新的I/O事件,node在一些特殊情況下會阻塞在這里。

        檢查階段(check): setImmediate()的回調會在這個階段執(zhí)行。

        關閉事件回調階段(close callbacks): 例如socket.on('close', ...)這種close事件的回調

        poll:

        這個階段是輪詢時間,用于等待還未返回的 I/O 事件,比如服務器的回應、用戶移動鼠標等等。

        這個階段的時間會比較長。如果沒有其他異步任務要處理(比如到期的定時器),會一直停留在這個階段,等待 I/O 請求返回結果。

        check:

        該階段執(zhí)行setImmediate()的回調函數(shù)。


        close:

        該階段執(zhí)行關閉請求的回調函數(shù),比如socket.on('close', ...)。


        timer階段:

        這個是定時器階段,處理setTimeout()和setInterval()的回調函數(shù)。進入這個階段后,主線程會檢查一下當前時間,是否滿足定時器的條件。如果滿足就執(zhí)行回調函數(shù),否則就離開這個階段。


        I/O callback階段:

        除了以下的回調函數(shù),其他都在這個階段執(zhí)行:


        setTimeout()和setInterval()的回調函數(shù)

        setImmediate()的回調函數(shù)

        用于關閉請求的回調函數(shù),比如socket.on('close', ...)

        宏任務和微任務

        宏任務:


        setImmediate

        setTimeout

        setInterval

        script(整體代碼)

        I/O 操作等。

        微任務:


        process.nextTick

        new Promise().then(回調)

        Promise.nextTick, setTimeout, setImmediate的使用場景和區(qū)別

        Promise.nextTick

        process.nextTick 是一個獨立于 eventLoop 的任務隊列。

        在每一個 eventLoop 階段完成后會去檢查 nextTick 隊列,如果里面有任務,會讓這部分任務優(yōu)先于微任務執(zhí)行。

        是所有異步任務中最快執(zhí)行的。


        setTimeout:

        setTimeout()方法是定義一個回調,并且希望這個回調在我們所指定的時間間隔后第一時間去執(zhí)行。


        setImmediate:

        setImmediate()方法從意義上將是立刻執(zhí)行的意思,但是實際上它卻是在一個固定的階段才會執(zhí)行回調,即poll階段之后。


        經(jīng)典題目分析

        一. 下面代碼輸出什么

        async function async1() {

           console.log('async1 start');

           await async2();

           console.log('async1 end');

        }

        async function async2() {

           console.log('async2');

        }

        console.log('script start');

        setTimeout(function() {

           console.log('setTimeout');

        }, 0)

        async1();

        new Promise(function(resolve) {

           console.log('promise1');

           resolve();

        }).then(function() {

           console.log('promise2');

        });

        console.log('script end');

        先執(zhí)行宏任務(當前代碼塊也算是宏任務),然后執(zhí)行當前宏任務產(chǎn)生的微任務,然后接著執(zhí)行宏任務


        從上往下執(zhí)行代碼,先執(zhí)行同步代碼,輸出 script start

        遇到setTimeout,現(xiàn)把 setTimeout 的代碼放到宏任務隊列中

        執(zhí)行 async1(),輸出 async1 start, 然后執(zhí)行 async2(), 輸出 async2,把 async2() 后面的代碼 console.log('async1 end')放到微任務隊列中

        接著往下執(zhí)行,輸出 promise1,把 .then()放到微任務隊列中;注意Promise本身是同步的立即執(zhí)行函數(shù),.then是異步執(zhí)行函數(shù)

        接著往下執(zhí)行, 輸出 script end。同步代碼(同時也是宏任務)執(zhí)行完成,接下來開始執(zhí)行剛才放到微任務中的代碼

        依次執(zhí)行微任務中的代碼,依次輸出 async1 end、 promise2, 微任務中的代碼執(zhí)行完成后,開始執(zhí)行宏任務中的代碼,輸出 setTimeout

        最后的執(zhí)行結果如下


        script start

        async1 start

        async2

        promise1

        script end

        async1 end

        promise2

        setTimeout

        二. 下面代碼輸出什么

        console.log('start');

        setTimeout(() => {

           console.log('children2');

           Promise.resolve().then(() => {

               console.log('children3');

           })

        }, 0);


        new Promise(function(resolve, reject) {

           console.log('children4');

           setTimeout(function() {

               console.log('children5');

               resolve('children6')

           }, 0)

        }).then((res) => {

           console.log('children7');

           setTimeout(() => {

               console.log(res);

           }, 0)

        })

        這道題跟上面題目不同之處在于,執(zhí)行代碼會產(chǎn)生很多個宏任務,每個宏任務中又會產(chǎn)生微任務


        從上往下執(zhí)行代碼,先執(zhí)行同步代碼,輸出 start

        遇到setTimeout,先把 setTimeout 的代碼放到宏任務隊列①中

        接著往下執(zhí)行,輸出 children4, 遇到setTimeout,先把 setTimeout 的代碼放到宏任務隊列②中,此時.then并不會被放到微任務隊列中,因為 resolve是放到 setTimeout中執(zhí)行的

        代碼執(zhí)行完成之后,會查找微任務隊列中的事件,發(fā)現(xiàn)并沒有,于是開始執(zhí)行宏任務①,即第一個 setTimeout, 輸出 children2,此時,會把 Promise.resolve().then放到微任務隊列中。

        宏任務①中的代碼執(zhí)行完成后,會查找微任務隊列,于是輸出 children3;然后開始執(zhí)行宏任務②,即第二個 setTimeout,輸出 children5,此時將.then放到微任務隊列中。

        宏任務②中的代碼執(zhí)行完成后,會查找微任務隊列,于是輸出 children7,遇到 setTimeout,放到宏任務隊列中。此時微任務執(zhí)行完成,開始執(zhí)行宏任務,輸出 children6;

        最后的執(zhí)行結果如下


        start

        children4

        children2

        children3

        children5

        children7

        children6

        三. 下面代碼輸出什么

        const p = function() {

           return new Promise((resolve, reject) => {

               const p1 = new Promise((resolve, reject) => {

                   setTimeout(() => {

                       resolve(1)

                   }, 0)

                   resolve(2)

               })

               p1.then((res) => {

                   console.log(res);

               })

               console.log(3);

               resolve(4);

           })

        }



        p().then((res) => {

           console.log(res);

        })

        console.log('end');

        執(zhí)行代碼,Promise本身是同步的立即執(zhí)行函數(shù),.then是異步執(zhí)行函數(shù)。遇到setTimeout,先把其放入宏任務隊列中,遇到p1.then會先放到微任務隊列中,接著往下執(zhí)行,輸出 3

        遇到 p().then 會先放到微任務隊列中,接著往下執(zhí)行,輸出 end

        同步代碼塊執(zhí)行完成后,開始執(zhí)行微任務隊列中的任務,首先執(zhí)行 p1.then,輸出 2, 接著執(zhí)行p().then, 輸出 4

        微任務執(zhí)行完成后,開始執(zhí)行宏任務,setTimeout, resolve(1),但是此時 p1.then已經(jīng)執(zhí)行完成,此時 1不會輸出。

        最后的執(zhí)行結果如下


        3

        end

        2

        4

        你可以將上述代碼中的 resolve(2)注釋掉, 此時 1才會輸出,輸出結果為 3 end 4 1。


        const p = function() {

           return new Promise((resolve, reject) => {

               const p1 = new Promise((resolve, reject) => {

                   setTimeout(() => {

                       resolve(1)

                   }, 0)

               })

               p1.then((res) => {

                   console.log(res);

               })

               console.log(3);

               resolve(4);

           })

        }



        p().then((res) => {

           console.log(res);

        })

        console.log('end');

        3

        end

        4

        1

        最后強烈推薦幾個非常好的講解 event loop 的視頻:


        What the heck is the event loop anyway? | Philip Roberts | JSConf EU

        Jake Archibald: In The Loop - JSConf.Asia

        日歷

        鏈接

        個人資料

        藍藍設計的小編 http://www.shtzxx.cn

        存檔

        主站蜘蛛池模板: 野花韩国高清免费神马| 极品少妇的诱惑| 精品一区二区三区在线成人| 汉服女装齐胸襦裙喷水视频| 成全影院电视剧在线观看| 人人爽久久涩噜噜噜av| 曰韩无码av一区二区免费| 性欧美乱熟妇xxxx白浆| 无遮挡又黄又刺激又爽的视频 | 国产精品视频免费播放| 加勒比hezyo黑人专区| 国产精品国产三级国产aⅴ下载| 久热这里只有精品视频6| 韩国午夜理论在线观看 | 秋霞在线观看秋| 国产精品久久久福利| 国产精品igao视频网| 亚洲精品一卡2卡3卡四卡乱码| 孕交videosgratis孕妇| 国产精品女同一区二区| 国产手机在线精品| 99re热这里只有精品视频| 滨州市| 杨浦区| 精精国产xxxx视频在线| 一个人的bd国语高清在线观看| 综合成人亚洲网友偷自拍| 国产在线98福利播放视频| 国产精品高潮呻吟av久久4虎| 欧美区一区二区三区| 少妇人妻偷人激情视频| 国产美女视频免费的| 三个男吃我奶头一边一个视频| 欧美牲交a欧美牲交aⅴ免费下载| 性姿势真人免费视频放| 国产无套内射又大又猛又粗又爽| ysl蜜桃色7425| 崇阳县| 免费a级毛片出奶水| 五月丁香六月综合缴清无码| 南阳市|