摘要:瀏覽器是多進(jìn)程的,而瀏覽器的內(nèi)核渲染進(jìn)程是多線程的。如果已經(jīng)將回調(diào)函數(shù)放進(jìn)任務(wù)隊(duì)列,但是主線程正在執(zhí)行一個(gè)非常耗時(shí)的任務(wù),當(dāng)這個(gè)任務(wù)執(zhí)行完畢后,主線程去任務(wù)隊(duì)列中取任務(wù),這個(gè)時(shí)候,就會(huì)出現(xiàn)連續(xù)執(zhí)行的情況,也就是說相當(dāng)于失效了。
前言
??在刷筆試題的時(shí)候,經(jīng)常會(huì)碰到setTimeout的問題,只知道這個(gè)是設(shè)置定時(shí)器;但是考察的重點(diǎn)一般是在一個(gè)方法中包含了定時(shí)器,定時(shí)器中的打印和方法中打印的執(zhí)行順序問題,也許我說的有點(diǎn)兒難懂,下面就來看看setTimeout到底是什么吧!
定時(shí)器的介紹 js中有哪些定時(shí)器? 周期定時(shí)器:setInterval()??setInterval()是按照指定的周期來調(diào)用定時(shí)器,方法會(huì)不斷的調(diào)用定時(shí)器,直到使用clearInterval()停止或者窗口關(guān)閉
??setInterval(code,millisec,lang)
code:要執(zhí)行的方法體(必選)
millisec:每隔多少毫秒執(zhí)行一次(單位是毫秒,如果設(shè)置為5000,即每5秒執(zhí)行一次)(必選)
lang:指使用的語(yǔ)言(可選)
??通過setInterval實(shí)現(xiàn)時(shí)鐘效果
??效果圖:
‘一次性’定時(shí)器:setTimeout()??顧名思義,這個(gè)定時(shí)器只會(huì)執(zhí)行一次,和setInterval()的區(qū)別就在這兒了,正是因?yàn)槿绱耍瑂etInterval()才需要使用clearInterval方法去取消定時(shí)器
??setTimeout(code,millisec,lang)????ps:每個(gè)參數(shù)的含義和setInterval()的均相同
??點(diǎn)擊按鈕3秒后彈出“Hello”
菜鳥教程(runoob.com) 點(diǎn)擊按鈕,在等待 3 秒后彈出 "Hello"。
??效果圖:
??使用計(jì)時(shí)器ID來取消計(jì)時(shí)器回調(diào)的發(fā)生,每個(gè)計(jì)時(shí)器都會(huì)返回一個(gè)id,是為了取消定時(shí)器的方法可以獲取到相應(yīng)的計(jì)數(shù)器。
clearInterval(id)
clearTimeout(id)
//設(shè)置超時(shí)調(diào)用 var timeoutId = setTimeout(function (){ alert("hello World"); },1000); //取消掉用的代碼 clearTimeout(timeoutId);setTimeout的執(zhí)行順序到底是怎樣的?
??我們都知道,js是單線程語(yǔ)言,所有的多線程都是假象,都是單線程模擬出來的。瀏覽器是多進(jìn)程的,而瀏覽器的內(nèi)核(渲染進(jìn)程)是多線程的。不理解這句話的可以去看看這篇文章。
??渲染進(jìn)程中有一個(gè)js引擎線程,這個(gè)線程是用來處理javaScript腳本的(例如chrome的V8引擎),而我們一直說的javaScript是單線程的就是因?yàn)檫@個(gè)。
??那么問題來了,既然js是單線程的,那setTimeout的異步是怎么實(shí)現(xiàn)的呢?js在解析腳本的時(shí)候,會(huì)將任務(wù)分為兩大類,同步任務(wù)和異步任務(wù),它們?cè)诮馕鰰r(shí)會(huì)進(jìn)入不同的場(chǎng)所執(zhí)行。
同步任務(wù):會(huì)進(jìn)入主線程的執(zhí)行棧,也就是js引擎線程管理的地方,按照順序執(zhí)行
異步任務(wù):進(jìn)入Event Table中,并注冊(cè)函數(shù),當(dāng)回調(diào)函數(shù)的條件滿足時(shí),就會(huì)將回調(diào)函數(shù)放進(jìn)Event Queue中,也就是任務(wù)隊(duì)列中。
??當(dāng)主線程中的任務(wù)執(zhí)行完畢后,也就是執(zhí)行棧為空時(shí),就會(huì)去任務(wù)隊(duì)列中看有沒有事件,如果有的話,就進(jìn)入主線程執(zhí)行,一直這樣循環(huán)下去,這就是事件循環(huán)機(jī)制了,可以參照下面的圖理解一下:
??也許你對(duì)事件循環(huán)機(jī)制的過程還是不太明白,那么我再解釋清楚一點(diǎn)。例如下面這個(gè)例子:
console.log("start") setTimeout(function(){ console.log("setTimeout") },5000) console.log("end")
執(zhí)行過程:
開始解析,遇到console.log,是同步任務(wù),進(jìn)入主線程,直接執(zhí)行,打印start;
往下走,遇到setTimeout,是異步任務(wù),進(jìn)入Event Table,并注冊(cè)回調(diào)函數(shù);
再往下走,遇到console.log,直接執(zhí)行,打印end;
5s后,將回調(diào)函數(shù)放進(jìn)Event Queue,此時(shí)執(zhí)行棧剛好為空,主線程會(huì)去任務(wù)隊(duì)列中取出這個(gè)回調(diào)函數(shù),執(zhí)行,打印setTimeout
??ps:
第1,3步都是js引擎線程干的事情,主線程執(zhí)行任務(wù);
第2步是渲染進(jìn)程中的事件觸發(fā)線程(專門管理任務(wù)隊(duì)列的)管理;
第4步是定時(shí)器線程控制的(也就是setTiemout和setInterval所在的進(jìn)程),定時(shí)器線程專門用來控制什么時(shí)候?qū)⒒卣{(diào)函數(shù)放進(jìn)任務(wù)隊(duì)列。
??如果看懂了上面的例子,就知道其實(shí)setTimeout的第二個(gè)參數(shù)其實(shí)并不能準(zhǔn)確的控制多少秒后執(zhí)行里面的函數(shù),而是控制多少秒后將這個(gè)函數(shù)放進(jìn)任務(wù)隊(duì)列中;這樣也就同樣可以解釋,為什么有時(shí)候明明設(shè)置的是2秒之后執(zhí)行,卻要等不止2秒(因?yàn)楹苡锌赡芏〞r(shí)線程將回調(diào)函數(shù)放進(jìn)任務(wù)隊(duì)列后,主線程還在執(zhí)行執(zhí)行棧中的任務(wù),需要執(zhí)行棧中的任務(wù)全部執(zhí)行完后才會(huì)去任務(wù)隊(duì)列中取任務(wù))。
??這樣就會(huì)引發(fā)一個(gè)問題,我們知道setInterval是隔一定的時(shí)間執(zhí)行一次,現(xiàn)在理解了原理后,就知道其實(shí)是隔一定的時(shí)間定時(shí)器線程將回調(diào)函數(shù)放進(jìn)任務(wù)隊(duì)列中。如果已經(jīng)將回調(diào)函數(shù)放進(jìn)任務(wù)隊(duì)列,但是主線程正在執(zhí)行一個(gè)非常耗時(shí)的任務(wù),當(dāng)這個(gè)任務(wù)執(zhí)行完畢后,主線程去任務(wù)隊(duì)列中取任務(wù),這個(gè)時(shí)候,就會(huì)出現(xiàn)連續(xù)執(zhí)行的情況,也就是說setInterval相當(dāng)于失效了。
??這一部分主要是針對(duì)在事件循環(huán)機(jī)制中setTimeout調(diào)順序進(jìn)行舉例子,如果能夠輕松的將例子看懂,就說明你是真的懂了事件循環(huán)機(jī)制的一部分,為什么說是一部分呢,因?yàn)檫€有一個(gè)宏任務(wù)和微任務(wù)的知識(shí)點(diǎn)還沒有涉及到,后面的進(jìn)階篇就會(huì)涉及到啦!
例1console.log("start") setTimeout(function(){ console.log("setTimeout") },0) console.log("end")
打印結(jié)果:(如果前面看懂了的同學(xué)應(yīng)該就會(huì)明白為什么)
分析:其實(shí)和上面那個(gè)例子時(shí)一樣的,只是這個(gè)0會(huì)給我們一種會(huì)立即執(zhí)行的假象,這個(gè)0是說明定時(shí)器線程會(huì)立即將回調(diào)函數(shù)放進(jìn)任務(wù)隊(duì)列而已,主線程還是會(huì)將執(zhí)行棧中的兩個(gè)同步任務(wù)執(zhí)行完成后再去任務(wù)隊(duì)列中取任務(wù),所以執(zhí)行順序和這里的秒數(shù)無關(guān)。而且即使執(zhí)行棧為空,也不會(huì)0秒就執(zhí)行,因?yàn)镠TML的標(biāo)準(zhǔn)規(guī)定,setTimeout不超過4ms按照4ms來計(jì)算。
例2console.log("start") setTimeout(function(){ console.log("setTimeout") }(),0) console.log("end")
打印結(jié)果:(仔細(xì)對(duì)比與例1的區(qū)別)
分析:細(xì)心的同學(xué)會(huì)發(fā)現(xiàn),我將回調(diào)函數(shù)改成了立即執(zhí)行函數(shù),就改變了執(zhí)行的順序。首先我們需要明確的是setTimeout的第一個(gè)參數(shù)是指函數(shù)的返回值,這里回調(diào)函數(shù)為立即執(zhí)行函數(shù)時(shí),返回值就是undefined了,所以會(huì)直接執(zhí)行立即執(zhí)行函數(shù),也就是立即打印setTimeout,而真正的setTimeout函數(shù)就相當(dāng)于沒起作用。
例3setTimeout(() => { console.log("setTimeout") },3000) sleep(10000000)//偽代碼,表示這個(gè)函數(shù)要執(zhí)行很久很久
打印結(jié)果:
這個(gè)結(jié)果不說也知道,肯定會(huì)打印出setTimeout的,但是重點(diǎn)卻不在這兒~
重點(diǎn)在于,這個(gè)setTimeout是隔很久很久打印出來的,遠(yuǎn)遠(yuǎn)超過了3秒,這個(gè)例子也是很明確的體現(xiàn)了js的事件循環(huán)機(jī)制。
??這一部分相對(duì)于基礎(chǔ)篇,加上了作用域以及其他也是比較難以理解的東西,可能還需要補(bǔ)充一些其他知識(shí)才會(huì)明白,我會(huì)盡量講清楚,也會(huì)把我看的參考文章放在下面。
??受到一篇文章的啟發(fā),我們以循序漸進(jìn)的方式來闡述
問題:以下代碼輸出的是什么?
for(var i = 0;i < 5;i++){ console.log(i) }
答案:沒錯(cuò),你沒有看錯(cuò),就是一個(gè)簡(jiǎn)單的循環(huán),就像你想的那樣,連續(xù)輸出0,1,2,3,4
難度:OO問題:以下代碼輸出的是什么?如果把時(shí)間改為1000*i輸出的又是什么?
for(var i = 0;i < 5;i++){ setTimeout(function(){ console.log(i) },1000) }
答案:
??時(shí)間為1000時(shí),1秒后會(huì)連續(xù)輸出5個(gè)5;時(shí)間為1000*i時(shí),會(huì)每隔一秒輸出一個(gè)5,一共5個(gè)5
分析:
??由上面的事件循環(huán)機(jī)制我們知道,setTimeout是異步事件,會(huì)放在事件隊(duì)列中等著主線程來執(zhí)行,這個(gè)時(shí)候for循環(huán)中的i已經(jīng)變成了5,由于定時(shí)器線程是在1秒后直接將5個(gè)setTimeout事件放進(jìn)事件隊(duì)列中,所以主線程在執(zhí)行的時(shí)候就沒有間隔了;當(dāng)時(shí)間乘上一個(gè)i時(shí),定時(shí)器會(huì)隔1秒將setTimeout事件放入隊(duì)列,就會(huì)出現(xiàn)每隔一秒輸出一個(gè)5的情況。
問題:如果想輸出0,1,2,3,4應(yīng)該怎么改?
分析:
??出現(xiàn)上一題的情況主要是因?yàn)樵趕etTimeout的回調(diào)函數(shù)中并沒有保存每次循環(huán)i的值,最后執(zhí)行的時(shí)候,得到的i就是最后更新的i了(即為5),所以要解決這個(gè)問題,思路是要在回調(diào)函數(shù)中保存每次for循環(huán)中的i值。
解決方案1:使用es6中l(wèi)et代替var
分析:let是es6中新增的內(nèi)容,作用和var一樣,都是用來定義變量,但是最大的差別就是let會(huì)形成塊級(jí)作用域,在本例中,就是每次循環(huán),都會(huì)產(chǎn)生一個(gè)作用域,在該作用域中的變量是一個(gè)固定值,下次i變化時(shí)不會(huì)對(duì)這個(gè)i產(chǎn)生影響,也就是達(dá)到了我們的目標(biāo)。
for(let i = 0;i < 5;i++){ setTimeout(function(){ console.log(i) },1000*i) }
解決方案2:使用閉包
分析:就是直接在setTimeout函數(shù)的外面套一層立即執(zhí)行函數(shù),并將i值作為參數(shù)傳到匿名函數(shù)中(這里的匿名函數(shù)也可以是命名函數(shù)),然后由于setTimeout中回調(diào)函數(shù)用到了匿名函數(shù)中的i,就會(huì)形成閉包。
for(var i = 0;i < 5;i++){ (function(i){ setTimeout(function(){ console.log(i) }, 1000 * i) }) (i) }
延伸:將代碼變成下面這樣會(huì)輸出什么?(去掉匿名函數(shù)中的i)
分析:這里會(huì)輸出5個(gè)5,也就是閉包沒有起作用,根本原因是i并沒有傳進(jìn)去,打印的還是最后的i
for (var i = 0; i < 5; i++) { (function () { setTimeout(function () { console.log(i) }, i * 1000) })(i); }
解決方案3:將回調(diào)函數(shù)改成立即執(zhí)行函數(shù)
分析:這個(gè)解決方案其實(shí)不是太好,如果要求是每隔1秒輸出一個(gè)數(shù)字,這個(gè)方法就不適用了;這個(gè)方法會(huì)立馬輸出0,1,2,3,4,原因結(jié)合基礎(chǔ)篇應(yīng)該就明白了
for (var i = 0; i < 5; i++) { setTimeout((function (i){ console.log(i); })(i), i * 1000) }難度:OOOO
??這一部分會(huì)涉及到promise,事件循環(huán)機(jī)制,宏任務(wù)和微任務(wù)的內(nèi)容,算是比較難的部分了,如果覺得比較難看懂,最好先去補(bǔ)一下基礎(chǔ)知識(shí),我這里就簡(jiǎn)單介紹一下。
promise對(duì)象我這里就不詳細(xì)講了,可以看這篇文章
宏任務(wù)和微任務(wù)宏任務(wù):可以理解成將代碼塊走一遍的過程,setTimeout和promise都是宏任務(wù),現(xiàn)在不理解沒關(guān)系,后面會(huì)通過例子幫助理解
微任務(wù):是在宏任務(wù)執(zhí)行完成之后執(zhí)行的,也是有相應(yīng)的微任務(wù)隊(duì)列存放微任務(wù),比如promise中的then就是微任務(wù)
問題:以下代碼輸出的是什么?
setTimeout(function () { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for (var i = 0; i < 10000; i++) { i == 9999 && resolve(); } console.log(3); }).then(function () { console.log(4); }); console.log(5);
答案:(是不是很懵,為什么會(huì)是這樣,下面看我的分析你就知道了)
分析:
進(jìn)入宏任務(wù)(從第一行到最后一行執(zhí)行一遍的過程),碰到setTimeout,將setTimeout放進(jìn)事件隊(duì)列中;
碰到promise,執(zhí)行console,打印2;
經(jīng)過循環(huán)后,執(zhí)行console,打印3;
到了then,由于then是微任務(wù),會(huì)在宏任務(wù)執(zhí)行完成后執(zhí)行,放進(jìn)微任務(wù)隊(duì)列;
遇到console,打印5;
至此,第一次的宏任務(wù)執(zhí)行完成,接下來執(zhí)行微任務(wù)隊(duì)列中的then,打印4;
現(xiàn)在執(zhí)行棧中的任務(wù)都執(zhí)行完了,現(xiàn)在就要去事件隊(duì)列中取事件,此時(shí)執(zhí)行setTimeout這個(gè)宏任務(wù),打印1;
宏任務(wù)微任務(wù)與同步事件異步事件的關(guān)系:
??這些詞都是用來描述事件的,只是從不同的角度來描述,就像是胖子矮子與男生女生之間的聯(lián)系。
??關(guān)于setTimeout還有很多可以去研究的東西,我這里只是將我目前看到的相關(guān)內(nèi)容進(jìn)行總結(jié),由于涉及的內(nèi)容過多,如果沒有相關(guān)內(nèi)容的基礎(chǔ)可能會(huì)比較難看懂,我也是為了這篇文章看了好多資料,這篇文章拖了大概一周才完工,有什么問題,可以留言告訴我呀!
??如果你覺得還不錯(cuò),就請(qǐng)給個(gè)贊吧~
參考文章關(guān)于setTimeout的面試題
js事件執(zhí)行機(jī)制
從瀏覽器進(jìn)程到j(luò)s線程的詳解
強(qiáng)烈推薦js進(jìn)階系列
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/99372.html
摘要:拿到秋招的同學(xué),如確定入職需與用人單位簽署三方協(xié)議,以保證雙方的利益不受損失。當(dāng)然每個(gè)崗位所要求的側(cè)重點(diǎn)不同,但卻百變不離其宗。方法論要想達(dá)成某個(gè)目標(biāo)都有其特定的方法論,學(xué)習(xí)技術(shù)也不例外,掌握適當(dāng)?shù)膶W(xué)習(xí)方法才能事半功倍。 寫在前面的話 筆者從17年的2月份開始準(zhǔn)備春招,其中遇到不少坑,也意識(shí)到自己走過的彎路。故寫了這篇文章總結(jié)一番,本文適合主動(dòng)學(xué)習(xí)的,對(duì)自己要學(xué)的課程不明確的,對(duì)面試有...
摘要:拿到秋招的同學(xué),如確定入職需與用人單位簽署三方協(xié)議,以保證雙方的利益不受損失。當(dāng)然每個(gè)崗位所要求的側(cè)重點(diǎn)不同,但卻百變不離其宗。方法論要想達(dá)成某個(gè)目標(biāo)都有其特定的方法論,學(xué)習(xí)技術(shù)也不例外,掌握適當(dāng)?shù)膶W(xué)習(xí)方法才能事半功倍。 寫在前面的話 筆者從17年的2月份開始準(zhǔn)備春招,其中遇到不少坑,也意識(shí)到自己走過的彎路。故寫了這篇文章總結(jié)一番,本文適合主動(dòng)學(xué)習(xí)的,對(duì)自己要學(xué)的課程不明確的,對(duì)面試有...
摘要:思路查找倒數(shù)第個(gè)節(jié)點(diǎn),可以看做是查找正序第個(gè)節(jié)點(diǎn)可以根據(jù)第一題的結(jié)果取數(shù)組的第個(gè)節(jié)點(diǎn)使用思路輸入一個(gè)鏈表,反轉(zhuǎn)鏈表后,輸出新鏈表的表頭。 前言 ??在寫項(xiàng)目的時(shí)候會(huì)發(fā)現(xiàn),并沒有使用很多關(guān)于鏈表的東西,大多數(shù)情況使用的都是數(shù)組,但是由于在準(zhǔn)備校招,很多公司都會(huì)考到這個(gè)問題,所以準(zhǔn)備對(duì)鏈表的相關(guān)操作進(jìn)行總結(jié),并對(duì)其中的重難點(diǎn)進(jìn)行強(qiáng)調(diào),最后還會(huì)附加幾道關(guān)于鏈表的算法題,那么現(xiàn)在就開始吧! ...
摘要:背景個(gè)人背景就讀于東北某普通二本院校計(jì)算機(jī)軟件工程專業(yè),現(xiàn)大四,北京實(shí)習(xí)前端方向,自學(xué),技術(shù)棧時(shí)間背景大概是在月日準(zhǔn)備好簡(jiǎn)歷開始投遞秋招差不多已經(jīng)結(jié)束招聘崗位不多,投遞對(duì)象為大一些的互聯(lián)網(wǎng)公司事件背景第一個(gè)入職的是好未來的前端實(shí)習(xí)崗,待遇工 背景 個(gè)人背景 就讀于東北某普通二本院校計(jì)算機(jī)軟件工程專業(yè),現(xiàn)大四,北京實(shí)習(xí) 前端方向,自學(xué),vue技術(shù)棧 時(shí)間背景 大概是在11月9日準(zhǔn)備...
摘要:背景個(gè)人背景就讀于東北某普通二本院校計(jì)算機(jī)軟件工程專業(yè),現(xiàn)大四,北京實(shí)習(xí)前端方向,自學(xué),技術(shù)棧時(shí)間背景大概是在月日準(zhǔn)備好簡(jiǎn)歷開始投遞秋招差不多已經(jīng)結(jié)束招聘崗位不多,投遞對(duì)象為大一些的互聯(lián)網(wǎng)公司事件背景第一個(gè)入職的是好未來的前端實(shí)習(xí)崗,待遇工 背景 個(gè)人背景 就讀于東北某普通二本院校計(jì)算機(jī)軟件工程專業(yè),現(xiàn)大四,北京實(shí)習(xí) 前端方向,自學(xué),vue技術(shù)棧 時(shí)間背景 大概是在11月9日準(zhǔn)備...
閱讀 2844·2021-11-19 11:35
閱讀 2591·2021-11-02 14:40
閱讀 1412·2021-09-04 16:48
閱讀 3019·2019-08-30 15:55
閱讀 1773·2019-08-30 13:11
閱讀 1965·2019-08-29 11:12
閱讀 1101·2019-08-27 10:52
閱讀 3169·2019-08-26 18:36