標籤:lan 單線程 嵌套 gpo 時間 media next tin 做了
一直沒有深入瞭解過JavaScript的事件執行機制,直到看到了這篇文章:《這一次,徹底弄懂JavaScript執行機制》 才發覺熟悉JavaScript的執行機制非常重要。
畢竟在跟進項目中偶爾需要排查為什麼會出現函數執行順序不一樣的情況。
感謝作者淺顯易懂的文字讓我獲益匪淺,以下是自己對JavaScript執行機制的理解,全是流水賬。
文章主要敘述:
1:單線程和非同步任務
2: 非同步任務的分類
3:setTimeout 和 setInterval 的執行方式
單線程和非同步任務
JavaScript的特點就是單線程,也就是說,同一個時間只能做一件事。換句話說就是一行一行地按照順序執行代碼:
console.log(1);let timeId = setTimeout(() => { console.log(2);},0);
console.log(3);
運行上面的代碼預想中的是1,2,3。但實際上列印出來的是:1,3,2
從上面的運行結果來看JavaScript等所有的同步任務執行完之後,再去執行非同步任務
這是因為雖然JavaScript是單線程操作,但如果不做一些處理遇到類似setTimeout之類的非同步作業就會導致阻塞。
這裡有一個疑問了,在主線程執行同步任務的過程中,非同步任務就真的任何事情都不做了嗎?
實際上:
除了主線程之外,還存在一個"任務隊列"(task queue)。
非同步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務。
只要非同步任務有了運行結果,就在"任務隊列"之中放置一個事件。然後通知主線程,某個非同步任務可以執行了,該任務才會進入主線程執行。
等主線程執行完同步任務後就會回過頭去尋找任務隊列中有哪些事件,事件對應的非同步任務進入執行棧,開始執行。執行完該任務後再去尋找任務隊列中可執行檔非同步任務,直到任務隊列清空。
所以這裡執行順序是這樣的:
1:執行console.log(1)
2:遇到timeId是非同步任務,先放到任務隊列
3:立即執行console.log(3)
4:同步任務執行完畢,去尋找任務隊列裡面的事件,發現timeId有運行結果了,執行timeId。隊列中沒有其他的事件了,主線程運行完畢。
再來看段代碼:
let timeId = setTimeout(() => { function task() { console.log(‘會2秒之後運行嗎‘); } task(); },2000); let oldTime = new Date(); function sleep(){ let newTime = new Date(); console.log(‘time:‘ + ( newTime.getSeconds() - oldTime.getSeconds())); if( newTime.getSeconds() - oldTime.getSeconds() < 5) { sleep(); } } sleep();
運行上面代碼,發現task並沒有在2秒之內執行而是在5秒之後才執行。
這是因為雖然timeId暫時被掛起,並且在2秒後有了運行結果後在"任務隊列"之中放置一個事件通知主線程timeId可以執行了。
但因為主線程中的同步任務sleep要5秒之後才運行完畢,導致執行棧5秒後才去任務隊列中執行等待中的timeId函數
(好比約了朋友去玩,出門前要換衣服,要約滴滴打車。用手機約好車之後就去換衣服,但是換衣服實在太久了,車都來了衣服還沒換好。司機打電話說我到了,你快過來坐車吧。
我跟司機說,衣服沒換好,你先等等吧。過了半小時之後終於換好了衣服(司機等得要砍人了)終於可以去坐車了)
總得來說其實JavaScript只有一個主線程,運行過程中碰到非同步任務就先掛起。而有了運行結果表示準備好了可以執行的非同步任務就進入執行棧中在同步任務後面去排隊。
非同步任務的分類:
macro-task(宏任務):setTimeout、setInterval、setImmediate、I/O(ajax)、UI互動事件(onClick,onScroll...)
micro-task(微任務):Promise、process.nextTick、MutaionObserver
不同的API註冊的非同步任務會依次進入自身對應的隊列中,然後等待Event Loop(事件迴圈)將它們依次放入主線程中執行
進入整體代碼(宏任務)後,開始第一次迴圈。接著執行所有的微任務。然後再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行所有的微任務
測試一下:
console.log(1);let timeId = setTimeout(() => { console.log(2); let Promise02 = new Promise(resolve => { console.log(3); resolve(); }).then(() => { console.log(4); });},0);let Promise01 = new Promise(resolve => { console.log(5); resolve();}).then(() => { console.log(6);});console.log(7);
列印出來是:1,5,7,6,2,3,4
所以執行順序實際上是這樣的:
1:執行宏任務(整體代碼)
2:執行微任務(Promise01的then方法)
3:執行宏任務(timeId)
4:執行微任務(timeId裡面Promise02的then方法)
setTimeout和setInterval的執行方式
setTimeout和setInterval都是非同步可以延時執行的方法。
setTimeout(fn,ms)到了ms秒後fn會進入任務隊列去等待執行,而setInterval(fn, ms)則是每到了ms秒都會有一個fn進入任務隊列去排隊等待執行,直到某個條件結束。
關於setTimeout(fn,ms)中的ms設定:
let timeId01 = setTimeout(() => { console.log(1);},1);let timeId02 = setTimeout(() => { console.log(2);},0);
上面代碼中timeId01的timer設定為1,timeId02的timer設定為0。按理說timeId02的執行順序比timeId01要優先,但實際上列印結果是:1,2
這是因為一般來說timer最小隻能設定4ms(嵌套層超過5,並且設定的timeout的時間少於4才有效)。但為了向實現看齊可最小設定1ms,0ms的時候會被設定為1ms
其中setTimeout(fn,0)
的含義是,指定某個任務在主線程最早可得的空閑時間執行,意思就是不用再等多少秒了,只要主線程執行棧內的同步任務全部執行完成,棧為空白就馬上執行。
理解JavaScript的執行機制