您好,登錄后才能下訂單哦!
小編給大家分享一下怎么通過setTimeout理解JS運行機制,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
序
setTimeout()函數:用來指定某個函數或某段代碼在多少毫秒之后執行。它返回一個整數,表示定時器timer的編號,可以用來取消該定時器。
例子
console.log(1); setTimeout(function () { console.log(2); }, 0); console.log(3);
問:最后的打印順序是什么?(如果不了解js的運行機制就會答錯)
正確答案:1 3 2
解析:無論setTimeout的執行時間是0還是1000,結果都是先輸出3后輸出2,這就是面試官常常考查的js運行機制的問題,接下來我們要引入一個概念,JavaScript 是單線程的。
二、 JavaScript 單線程
JavasScript引擎是基于事件驅動和單線程執行的,JS引擎一直等待著任務隊列中任務的到來,然后加以處理,瀏覽器無論什么時候都只有一個JS線程在運行程序,即主線程。
通俗的說:JS在同一時間內只能做一件事,這也常被稱為 “阻塞式執行”。
任務隊列
那么單線程的JavasScript是怎么實現“非阻塞執行”呢?
答:異步容易實現非阻塞,所以在JavaScript中對于耗時的操作或者時間不確定的操作,使用異步就成了必然的選擇。
諸如事件點擊觸發回調函數、ajax通信、計時器這種異步處理是如何實現的呢?
答:任務隊列
所有任務可以分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。
任務隊列:一個先進先出的隊列,它里面存放著各種事件和任務。
同步任務
同步任務:在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務。
輸出
如:console.log()
變量的聲明
同步函數:如果在函數返回的時候,調用者就能夠拿到預期的返回值或者看到預期的效果,那么這個函數就是同步的。
異步任務
setTimeout和setInterval
DOM事件
Promise
process.nextTick
fs.readFile
http.get
異步函數:如果在函數返回的時候,調用者還不能夠得到預期結果,而是需要在將來通過一定的手段得到,那么這個函數就是異步的。
除此之外,任務隊列又分為macro-task(宏任務)與micro-task(微任務),在ES5標準中,它們被分別稱為task與job。
宏任務
I/O
setTimeout
setInterval
setImmdiate
requestAnimationFrame
微任務
process.nextTick
Promise
Promise.then
MutationObserver
宏任務和微任務的執行順序
一次事件循環中,先執行宏任務隊列里的一個任務,再把微任務隊列里的所有任務執行完畢,再去宏任務隊列取下一個宏任務執行。
注:在當前的微任務沒有執行完成時,是不會執行下一個宏任務的。
三、setTimeout運行機制
setTimeout 和 setInterval的運行機制是將指定的代碼移出本次執行,等到下一輪 Event Loop 時,再檢查是否到了指定時間。如果到了,就執行對應的代碼;如果不到,就等到再下一輪 Event Loop 時重新判斷。
這意味著,setTimeout指定的代碼,必須等到本次執行的所有同步代碼都執行完,才會執行。
優先關系:異步任務要掛起,先執行同步任務,同步任務執行完畢才會響應異步任務。
四、進階
console.log('A'); setTimeout(function () { console.log('B'); }, 0); while (1) {}
大家再猜一下這段程序輸出的結果會是什么?
答:A
注:建議先注釋掉while循環代碼塊的代碼,執行后強制刪除進程,不然會造成“假死”。
同步隊列輸出A之后,陷入while(true){}的死循環中,異步任務不會被執行。
類似的,有時addEventListener()方法監聽點擊事件click,用戶點了某個按鈕會卡死,就是因為當前JS正在處理同步隊列,無法將click觸發事件放入執行棧,不會執行,出現“假死”。
五、定時獲取接口更新數據
for (var i = 0; i < 4; i++) { setTimeout(function () { console.log(i); }, 1000); }
輸出結果為,隔1s后一起輸出:4 4 4 4
for循環是一個同步任務,為什么連續輸出四個4?
答:因為有隊列插入的時間,即使執行時間從1000改成0,還是輸出四個4。
那么這個問題是如何產生和解決的呢?請接著閱讀
異步隊列執行的時間
執行到異步任務的時候,會直接放到異步隊列中嗎?
答案是不一定的。
因為瀏覽器有個定時器(timer)模塊,定時器到了執行時間才會把異步任務放到異步隊列。
for循環體執行的過程中并沒有把setTimeout放到異步隊列中,只是交給定時器模塊了。4個循環體執行速度非常快(不到1毫秒)。定時器到了設置的時間才會把setTimeout語句放到異步隊列中。
即使setTimeout設置的執行時間為0毫秒,也按4毫秒算。
這就解釋了上題為什么會連續輸出四個4的原因。
HTML5 標準規定了setTimeout()的第二個參數的最小值,即最短間隔,不得低于4毫秒。如果低于這個值,就會自動增加。在此之前,老版本的瀏覽器都將最短間隔設為10毫秒。
利用閉包實現 setTimeout 間歇調用
for (let i = 0; i < 4; i++) { (function (j) { setTimeout(function () { console.log(j); }, 1000 * i) })(i); }
執行后,會隔1s輸出一個值,分別是:0 1 2 3
此方法巧妙利用IIFE聲明即執行的函數表達式來解決閉包造成的問題。
將var改為let,使用了ES6語法。
這里也可以用setInterval()方法來實現間歇調用。
詳見:setTimeout和setInterval的區別
利用JS中基本類型的參數傳遞是按值傳遞的特征實現
var output = function (i) { setTimeout(function () { console.log(i); }, 1000 * i) } for (let i = 0; i < 4; i++) { output(i); }
執行后,會隔1s輸出一個值,分別是:0 1 2 3
實現原理:傳過去的i值被復制了。
基于Promise的解決方案
const tasks = []; const output = (i) => new Promise((resolve) => { setTimeout(() => { console.log(i); resolve(); }, 1000 * i); }); //生成全部的異步操作 for (var i = 0; i < 5; i++) { tasks.push(output(i)); } //同步操作完成后,輸出最后的i Promise.all(tasks).then(() => { setTimeout(() => { console.log(i); }, 1000) })
執行后,會隔1s輸出一個值,分別是:0 1 2 3 4 5
優點:提高了代碼的可讀性。
注意:如果沒有處理Promise的reject,會導致錯誤被丟進黑洞。
使用ES7中的async await特性的解決方案(推薦)
const sleep = (timeountMS) => new Promise((resolve) => { setTimeout(resolve, timeountMS); }); (async () => { //聲明即執行的async for (var i = 0; i < 5; i++) { await sleep(1000); console.log(i); } await sleep(1000); console.log(i); })();
執行后,會隔1s輸出一個值,分別是:0 1 2 3 4 5
六、事件循環 Event Loop
主線程從任務隊列中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop。
有時候 setTimeout明明寫的延時3秒,實際卻5,6秒才執行函數,這又是因為什么?
答:setTimeout 并不能保證執行的時間,是否及時執行取決于 JavaScript 線程是擁擠還是空閑。
瀏覽器的JS引擎遇到setTimeout,拿走之后不會立即放入異步隊列,同步任務執行之后,timer模塊會到設置時間之后放到異步隊列中。js引擎發現同步隊列中沒有要執行的東西了,即運行棧空了就從異步隊列中讀取,然后放到運行棧中執行。所以setTimeout可能會多了等待線程的時間。
這時setTimeout函數體就變成了運行棧中的執行任務,運行棧空了,再監聽異步隊列中有沒有要執行的任務,如果有就繼續執行,如此循環,就叫Event Loop。
七、總結
JavaScript通過事件循環和瀏覽器各線程協調共同實現異步。同步可以保證順序一致,但是容易導致阻塞;異步可以解決阻塞問題,但是會改變順序性。
知識點梳理:
理解JS的單線程的概念:一段時間內做一件事
理解任務隊列:同步任務、異步任務
理解 Event Loop
理解哪些語句會放入異步任務隊列
理解語句放入異步任務隊列的時機
看完了這篇文章,相信你對“怎么通過setTimeout理解JS運行機制”有了一定的了解,如果想了解更多相關知識,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。