小編給大家分享一下怎么通過setTimeout理解JS運(yùn)行機(jī)制,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
我們提供的服務(wù)有:網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、西夏ssl等。為千余家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的西夏網(wǎng)站制作公司
序
setTimeout()函數(shù):用來指定某個(gè)函數(shù)或某段代碼在多少毫秒之后執(zhí)行。它返回一個(gè)整數(shù),表示定時(shí)器timer的編號(hào),可以用來取消該定時(shí)器。
例子
console.log(1); setTimeout(function () { console.log(2); }, 0); console.log(3);
問:最后的打印順序是什么?(如果不了解js的運(yùn)行機(jī)制就會(huì)答錯(cuò))
正確答案:1 3 2
解析:無論setTimeout的執(zhí)行時(shí)間是0還是1000,結(jié)果都是先輸出3后輸出2,這就是面試官常??疾榈膉s運(yùn)行機(jī)制的問題,接下來我們要引入一個(gè)概念,JavaScript 是單線程的。
二、 JavaScript 單線程
JavasScript引擎是基于事件驅(qū)動(dòng)和單線程執(zhí)行的,JS引擎一直等待著任務(wù)隊(duì)列中任務(wù)的到來,然后加以處理,瀏覽器無論什么時(shí)候都只有一個(gè)JS線程在運(yùn)行程序,即主線程。
通俗的說:JS在同一時(shí)間內(nèi)只能做一件事,這也常被稱為 “阻塞式執(zhí)行”。
任務(wù)隊(duì)列
那么單線程的JavasScript是怎么實(shí)現(xiàn)“非阻塞執(zhí)行”呢?
答:異步容易實(shí)現(xiàn)非阻塞,所以在JavaScript中對(duì)于耗時(shí)的操作或者時(shí)間不確定的操作,使用異步就成了必然的選擇。
諸如事件點(diǎn)擊觸發(fā)回調(diào)函數(shù)、ajax通信、計(jì)時(shí)器這種異步處理是如何實(shí)現(xiàn)的呢?
答:任務(wù)隊(duì)列
所有任務(wù)可以分成兩種,一種是同步任務(wù)(synchronous),另一種是異步任務(wù)(asynchronous)。
任務(wù)隊(duì)列:一個(gè)先進(jìn)先出的隊(duì)列,它里面存放著各種事件和任務(wù)。
同步任務(wù)
同步任務(wù):在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù)。
輸出
如:console.log()
變量的聲明
同步函數(shù):如果在函數(shù)返回的時(shí)候,調(diào)用者就能夠拿到預(yù)期的返回值或者看到預(yù)期的效果,那么這個(gè)函數(shù)就是同步的。
異步任務(wù)
setTimeout和setInterval
DOM事件
Promise
process.nextTick
fs.readFile
http.get
異步函數(shù):如果在函數(shù)返回的時(shí)候,調(diào)用者還不能夠得到預(yù)期結(jié)果,而是需要在將來通過一定的手段得到,那么這個(gè)函數(shù)就是異步的。
除此之外,任務(wù)隊(duì)列又分為macro-task(宏任務(wù))與micro-task(微任務(wù)),在ES5標(biāo)準(zhǔn)中,它們被分別稱為task與job。
宏任務(wù)
I/O
setTimeout
setInterval
setImmdiate
requestAnimationFrame
微任務(wù)
process.nextTick
Promise
Promise.then
MutationObserver
宏任務(wù)和微任務(wù)的執(zhí)行順序
一次事件循環(huán)中,先執(zhí)行宏任務(wù)隊(duì)列里的一個(gè)任務(wù),再把微任務(wù)隊(duì)列里的所有任務(wù)執(zhí)行完畢,再去宏任務(wù)隊(duì)列取下一個(gè)宏任務(wù)執(zhí)行。
注:在當(dāng)前的微任務(wù)沒有執(zhí)行完成時(shí),是不會(huì)執(zhí)行下一個(gè)宏任務(wù)的。
三、setTimeout運(yùn)行機(jī)制
setTimeout 和 setInterval的運(yùn)行機(jī)制是將指定的代碼移出本次執(zhí)行,等到下一輪 Event Loop 時(shí),再檢查是否到了指定時(shí)間。如果到了,就執(zhí)行對(duì)應(yīng)的代碼;如果不到,就等到再下一輪 Event Loop 時(shí)重新判斷。
這意味著,setTimeout指定的代碼,必須等到本次執(zhí)行的所有同步代碼都執(zhí)行完,才會(huì)執(zhí)行。
優(yōu)先關(guān)系:異步任務(wù)要掛起,先執(zhí)行同步任務(wù),同步任務(wù)執(zhí)行完畢才會(huì)響應(yīng)異步任務(wù)。
四、進(jìn)階
console.log('A'); setTimeout(function () { console.log('B'); }, 0); while (1) {}
大家再猜一下這段程序輸出的結(jié)果會(huì)是什么?
答:A
注:建議先注釋掉while循環(huán)代碼塊的代碼,執(zhí)行后強(qiáng)制刪除進(jìn)程,不然會(huì)造成“假死”。
同步隊(duì)列輸出A之后,陷入while(true){}的死循環(huán)中,異步任務(wù)不會(huì)被執(zhí)行。
類似的,有時(shí)addEventListener()方法監(jiān)聽點(diǎn)擊事件click,用戶點(diǎn)了某個(gè)按鈕會(huì)卡死,就是因?yàn)楫?dāng)前JS正在處理同步隊(duì)列,無法將click觸發(fā)事件放入執(zhí)行棧,不會(huì)執(zhí)行,出現(xiàn)“假死”。
五、定時(shí)獲取接口更新數(shù)據(jù)
for (var i = 0; i < 4; i++) { setTimeout(function () { console.log(i); }, 1000); }
輸出結(jié)果為,隔1s后一起輸出:4 4 4 4
for循環(huán)是一個(gè)同步任務(wù),為什么連續(xù)輸出四個(gè)4?
答:因?yàn)橛嘘?duì)列插入的時(shí)間,即使執(zhí)行時(shí)間從1000改成0,還是輸出四個(gè)4。
那么這個(gè)問題是如何產(chǎn)生和解決的呢?請(qǐng)接著閱讀
異步隊(duì)列執(zhí)行的時(shí)間
執(zhí)行到異步任務(wù)的時(shí)候,會(huì)直接放到異步隊(duì)列中嗎?
答案是不一定的。
因?yàn)闉g覽器有個(gè)定時(shí)器(timer)模塊,定時(shí)器到了執(zhí)行時(shí)間才會(huì)把異步任務(wù)放到異步隊(duì)列。
for循環(huán)體執(zhí)行的過程中并沒有把setTimeout放到異步隊(duì)列中,只是交給定時(shí)器模塊了。4個(gè)循環(huán)體執(zhí)行速度非常快(不到1毫秒)。定時(shí)器到了設(shè)置的時(shí)間才會(huì)把setTimeout語句放到異步隊(duì)列中。
即使setTimeout設(shè)置的執(zhí)行時(shí)間為0毫秒,也按4毫秒算。
這就解釋了上題為什么會(huì)連續(xù)輸出四個(gè)4的原因。
HTML5 標(biāo)準(zhǔn)規(guī)定了setTimeout()的第二個(gè)參數(shù)的最小值,即最短間隔,不得低于4毫秒。如果低于這個(gè)值,就會(huì)自動(dòng)增加。在此之前,老版本的瀏覽器都將最短間隔設(shè)為10毫秒。
利用閉包實(shí)現(xiàn) setTimeout 間歇調(diào)用
for (let i = 0; i < 4; i++) { (function (j) { setTimeout(function () { console.log(j); }, 1000 * i) })(i); }
執(zhí)行后,會(huì)隔1s輸出一個(gè)值,分別是:0 1 2 3
此方法巧妙利用IIFE聲明即執(zhí)行的函數(shù)表達(dá)式來解決閉包造成的問題。
將var改為let,使用了ES6語法。
這里也可以用setInterval()方法來實(shí)現(xiàn)間歇調(diào)用。
詳見:setTimeout和setInterval的區(qū)別
利用JS中基本類型的參數(shù)傳遞是按值傳遞的特征實(shí)現(xiàn)
var output = function (i) { setTimeout(function () { console.log(i); }, 1000 * i) } for (let i = 0; i < 4; i++) { output(i); }
執(zhí)行后,會(huì)隔1s輸出一個(gè)值,分別是:0 1 2 3
實(shí)現(xiàn)原理:傳過去的i值被復(fù)制了。
基于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) })
執(zhí)行后,會(huì)隔1s輸出一個(gè)值,分別是:0 1 2 3 4 5
優(yōu)點(diǎn):提高了代碼的可讀性。
注意:如果沒有處理Promise的reject,會(huì)導(dǎo)致錯(cuò)誤被丟進(jìn)黑洞。
使用ES7中的async await特性的解決方案(推薦)
const sleep = (timeountMS) => new Promise((resolve) => { setTimeout(resolve, timeountMS); }); (async () => { //聲明即執(zhí)行的async for (var i = 0; i < 5; i++) { await sleep(1000); console.log(i); } await sleep(1000); console.log(i); })();
執(zhí)行后,會(huì)隔1s輸出一個(gè)值,分別是:0 1 2 3 4 5
六、事件循環(huán) Event Loop
主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event Loop。
有時(shí)候 setTimeout明明寫的延時(shí)3秒,實(shí)際卻5,6秒才執(zhí)行函數(shù),這又是因?yàn)槭裁矗?/p>
答:setTimeout 并不能保證執(zhí)行的時(shí)間,是否及時(shí)執(zhí)行取決于 JavaScript 線程是擁擠還是空閑。
瀏覽器的JS引擎遇到setTimeout,拿走之后不會(huì)立即放入異步隊(duì)列,同步任務(wù)執(zhí)行之后,timer模塊會(huì)到設(shè)置時(shí)間之后放到異步隊(duì)列中。js引擎發(fā)現(xiàn)同步隊(duì)列中沒有要執(zhí)行的東西了,即運(yùn)行??樟司蛷漠惒疥?duì)列中讀取,然后放到運(yùn)行棧中執(zhí)行。所以setTimeout可能會(huì)多了等待線程的時(shí)間。
這時(shí)setTimeout函數(shù)體就變成了運(yùn)行棧中的執(zhí)行任務(wù),運(yùn)行??樟耍俦O(jiān)聽異步隊(duì)列中有沒有要執(zhí)行的任務(wù),如果有就繼續(xù)執(zhí)行,如此循環(huán),就叫Event Loop。
七、總結(jié)
JavaScript通過事件循環(huán)和瀏覽器各線程協(xié)調(diào)共同實(shí)現(xiàn)異步。同步可以保證順序一致,但是容易導(dǎo)致阻塞;異步可以解決阻塞問題,但是會(huì)改變順序性。
知識(shí)點(diǎn)梳理:
理解JS的單線程的概念:一段時(shí)間內(nèi)做一件事
理解任務(wù)隊(duì)列:同步任務(wù)、異步任務(wù)
理解 Event Loop
理解哪些語句會(huì)放入異步任務(wù)隊(duì)列
理解語句放入異步任務(wù)隊(duì)列的時(shí)機(jī)
看完了這篇文章,相信你對(duì)“怎么通過setTimeout理解JS運(yùn)行機(jī)制”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!
網(wǎng)站欄目:怎么通過setTimeout理解JS運(yùn)行機(jī)制
當(dāng)前地址:http://www.rwnh.cn/article4/jdjgie.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、網(wǎng)站維護(hù)、網(wǎng)站建設(shè)、建站公司、、關(guān)鍵詞優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)