摘要:提出標(biāo)準(zhǔn),允許腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作。所以,這個(gè)新標(biāo)準(zhǔn)并沒(méi)有改變單線程的本質(zhì)。事件循環(huán)主線程線程只會(huì)做一件事,就是從消息隊(duì)列里面取消息執(zhí)行消息,再取消息再執(zhí)行。工作線程是生產(chǎn)者,主線程是消費(fèi)者。
最近項(xiàng)目中遇到了一個(gè)場(chǎng)景,其實(shí)很常見(jiàn),就是定時(shí)獲取接口刷新數(shù)據(jù)。那么問(wèn)題來(lái)了,假設(shè)我設(shè)置的定時(shí)時(shí)間為1s,而數(shù)據(jù)接口返回大于1s,應(yīng)該用同步阻塞還是異步?我們先整理下js中定時(shí)器的相關(guān)知識(shí),再來(lái)看這個(gè)問(wèn)題。初識(shí)setTimeout 與 setInterval
先來(lái)簡(jiǎn)單認(rèn)識(shí),后面我們?cè)囋囉胹etTimeout 實(shí)現(xiàn) setInterval 的功能
setTimeout 延遲一段時(shí)間執(zhí)行一次 (Only one)
setTimeout(function, milliseconds, param1, param2, ...) clearTimeout() // 阻止定時(shí)器運(yùn)行 e.g. setTimeout(function(){ alert("Hello"); }, 3000); // 3s后彈出
setInterval 每隔一段時(shí)間執(zhí)行一次 (Many times)
setInterval(function, milliseconds, param1, param2, ...) e.g. setInterval(function(){ alert("Hello"); }, 3000); // 每隔3s彈出
setTimeout和setInterval的延時(shí)最小間隔是4ms(W3C在HTML標(biāo)準(zhǔn)中規(guī)定);在JavaScript中沒(méi)有任何代碼是立刻執(zhí)行的,但一旦進(jìn)程空閑就盡快執(zhí)行。這意味著無(wú)論是setTimeout還是setInterval,所設(shè)置的時(shí)間都只是n毫秒被添加到隊(duì)列中,而不是過(guò)n毫秒后立即執(zhí)行。進(jìn)程與線程,傻傻分不清楚
為了講清楚這兩個(gè)抽象的概念,我們借用阮大大借用的比喻,先來(lái)模擬一個(gè)場(chǎng)景:
這里有一個(gè)大型工廠
工廠里有若干車(chē)間,每次只能有一個(gè)車(chē)間在作業(yè)
每個(gè)車(chē)間里有若干房間,有若干工人在流水線作業(yè)
那么:
一個(gè)工廠對(duì)應(yīng)的就是計(jì)算機(jī)的一個(gè)CPU,平時(shí)講的多核就代表多個(gè)工廠
每個(gè)工廠里的車(chē)間,就是進(jìn)程,意味著同一時(shí)刻一個(gè)CPU只運(yùn)行一個(gè)進(jìn)程,其余進(jìn)程在怠工
這個(gè)運(yùn)行的車(chē)間(進(jìn)程)里的工人,就是線程,可以有多個(gè)工人(線程)協(xié)同完成一個(gè)任務(wù)
車(chē)間(進(jìn)程)里的房間,代表內(nèi)存。
再深入點(diǎn):
車(chē)間(進(jìn)程)里工人可以隨意在多個(gè)房間(內(nèi)存)之間走動(dòng),意味著一個(gè)進(jìn)程里,多個(gè)線程可以共享內(nèi)存
部分房間(內(nèi)存)有限,只允許一個(gè)工人(線程)使用,此時(shí)其他工人(線程)要等待
房間里有工人進(jìn)去后上鎖,其他工人需要等房間(內(nèi)存)里的工人(線程)開(kāi)鎖出來(lái)后,才能才進(jìn)去,這就是互斥鎖(Mutual exclusion,縮寫(xiě) Mutex)
有些房間只能容納部分的人,意味著部分內(nèi)存只能給有限的線程
再再深入:
如果同時(shí)有多個(gè)車(chē)間作業(yè),就是多進(jìn)程
如果一個(gè)車(chē)間里有多個(gè)工人協(xié)同作業(yè),就是多線程
當(dāng)然不同車(chē)間之間的工人也可以有相互協(xié)作,就需要協(xié)調(diào)機(jī)制
JavaScript 單線程總所周知,JavaScript 這門(mén)語(yǔ)言的核心特征,就是單線程(是指在JS引擎中負(fù)責(zé)解釋和執(zhí)行JavaScript代碼的線程只有一個(gè))。這和 JavaScript 最初設(shè)計(jì)是作為一門(mén) GUI 編程語(yǔ)言有關(guān),最初用于瀏覽器端,單一線程控制 GUI 是很普遍的做法。但這里特別要?jiǎng)潅€(gè)重點(diǎn),雖然JavaScript是單線程,但瀏覽器是多線程的?。?!例如Webkit或是Gecko引擎,可能有javascript引擎線程、界面渲染線程、瀏覽器事件觸發(fā)線程、Http請(qǐng)求線程,讀寫(xiě)文件的線程(例如在Node.js中)。ps:可能要總結(jié)一篇瀏覽器渲染的文章了。
HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個(gè)新標(biāo)準(zhǔn)并沒(méi)有改變JavaScript單線程的本質(zhì)。同步與異步,傻傻分不清楚
之前阮大大寫(xiě)了一篇《JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop》,然后被樸靈評(píng)注了,特別是同步異步的理解上,兩位大牛有很大的歧義。
同步(synchronous):假如一個(gè)函數(shù)返回時(shí),調(diào)用者就能夠得到預(yù)期結(jié)果(即拿到了預(yù)期的返回值或者看到了預(yù)期的效果),這就是同步函數(shù)。
e.g. alert("馬上能看到我拉"); console.log("也能馬上看到我哦");
異步(asynchronous):假如一個(gè)函數(shù)返回時(shí),調(diào)用者不能得到預(yù)期結(jié)果,需要通過(guò)一定手段才能獲得,這就是異步函數(shù)。
e.g. setTimeout(function() { // 過(guò)一段時(shí)間才能執(zhí)行我哦 }, 1000);異步構(gòu)成要素
一個(gè)異步過(guò)程通常是這樣的:主線程發(fā)起一個(gè)異步請(qǐng)求,相應(yīng)的工作線程(比如瀏覽器的其他線程)接收請(qǐng)求并告知主線程已收到(異步函數(shù)返回);主線程可以繼續(xù)執(zhí)行后面的代碼,同時(shí)工作線程執(zhí)行異步任務(wù);工作線程完成工作后,通知主線程;主線程收到通知后,執(zhí)行一定的動(dòng)作(調(diào)用回調(diào)函數(shù))。
發(fā)起(注冊(cè))函數(shù) -- 發(fā)起異步過(guò)程
回調(diào)函數(shù) -- 處理結(jié)果
e.g. setTimeout(fn, 1000); // setTimeout就是異步過(guò)程的發(fā)起函數(shù),fn是回調(diào)函數(shù)通信機(jī)制
異步過(guò)程的通信機(jī)制:工作線程將消息放到消息隊(duì)列,主線程通過(guò)事件循環(huán)過(guò)程去取消息。消息隊(duì)列 Message Queue
一個(gè)先進(jìn)先出的隊(duì)列,存放各類(lèi)消息。事件循環(huán) Event Loop
主線程(js線程)只會(huì)做一件事,就是從消息隊(duì)列里面取消息、執(zhí)行消息,再取消息、再執(zhí)行。消息隊(duì)列為空時(shí),就會(huì)等待直到消息隊(duì)列變成非空。只有當(dāng)前的消息執(zhí)行結(jié)束,才會(huì)去取下一個(gè)消息。這種機(jī)制就叫做事件循環(huán)機(jī)制Event Loop,取一個(gè)消息并執(zhí)行的過(guò)程叫做一次循環(huán)。
工作線程是生產(chǎn)者,主線程是消費(fèi)者。工作線程執(zhí)行異步任務(wù),執(zhí)行完成后把對(duì)應(yīng)的回調(diào)函數(shù)封裝成一條消息放到消息隊(duì)列中;主線程不斷地從消息隊(duì)列中取消息并執(zhí)行,當(dāng)消息隊(duì)列空時(shí)主線程阻塞,直到消息隊(duì)列再次非空。setTimeout(function, 0) 發(fā)生了什么
其實(shí)到這兒,應(yīng)該能很好解釋setTimeout(function, 0) 這個(gè)常用的“奇技淫巧”了。很簡(jiǎn)單,就是為了將function里的任務(wù)異步執(zhí)行,0不代表立即執(zhí)行,而是將任務(wù)推到消息隊(duì)列的最后,再由主線程的事件循環(huán)去調(diào)用它執(zhí)行。
HTML5 中規(guī)定setTimeout 的最小時(shí)間不是0ms,而是4ms。setInterval 缺點(diǎn)
再次強(qiáng)調(diào),定時(shí)器指定的時(shí)間間隔,表示的是何時(shí)將定時(shí)器的代碼添加到消息隊(duì)列,而不是何時(shí)執(zhí)行代碼。所以真正何時(shí)執(zhí)行代碼的時(shí)間是不能保證的,取決于何時(shí)被主線程的事件循環(huán)取到,并執(zhí)行。
setInterval(function, N)
那么顯而易見(jiàn),上面這段代碼意味著,每隔N秒把function事件推到消息隊(duì)列中,什么時(shí)候執(zhí)行?母雞??!
上圖可見(jiàn),setInterval每隔100ms往隊(duì)列中添加一個(gè)事件;100ms后,添加T1定時(shí)器代碼至隊(duì)列中,主線程中還有任務(wù)在執(zhí)行,所以等待,some event執(zhí)行結(jié)束后執(zhí)行T1定時(shí)器代碼;又過(guò)了100ms,T2定時(shí)器被添加到隊(duì)列中,主線程還在執(zhí)行T1代碼,所以等待;又過(guò)了100ms,理論上又要往隊(duì)列里推一個(gè)定時(shí)器代碼,但由于此時(shí)T2還在隊(duì)列中,所以T3不會(huì)被添加,結(jié)果就是此時(shí)被跳過(guò);這里我們可以看到,T1定時(shí)器執(zhí)行結(jié)束后馬上執(zhí)行了T2代碼,所以并沒(méi)有達(dá)到定時(shí)器的效果。
綜上所述,setInterval有兩個(gè)缺點(diǎn):
使用setInterval時(shí),某些間隔會(huì)被跳過(guò);
可能多個(gè)定時(shí)器會(huì)連續(xù)執(zhí)行;
鏈?zhǔn)絪etTimeoutsetTimeout(function () { // 任務(wù) setTimeout(arguments.callee, interval); }, interval)
警告:在嚴(yán)格模式下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee()。當(dāng)一個(gè)函數(shù)必須調(diào)用自身的時(shí)候, 避免使用 arguments.callee(), 通過(guò)要么給函數(shù)表達(dá)式一個(gè)名字,要么使用一個(gè)函數(shù)聲明.
上述函數(shù)每次執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)新的定時(shí)器,第二個(gè)setTimeout使用了arguments.callee()獲取當(dāng)前函數(shù)的引用,并且為其設(shè)置另一個(gè)定時(shí)器。好處:
在前一個(gè)定時(shí)器執(zhí)行完前,不會(huì)向隊(duì)列插入新的定時(shí)器(解決缺點(diǎn)一)
保證定時(shí)器間隔(解決缺點(diǎn)二)
So...回顧最開(kāi)始的業(yè)務(wù)場(chǎng)景的問(wèn)題,用同步阻塞還是異步,答案已經(jīng)出來(lái)了...
PS:其實(shí)還有macrotask與microtask等知識(shí)點(diǎn)沒(méi)有提到,總結(jié)了那么多,其實(shí)JavaScript深入下去還有很多,任重而道遠(yuǎn)呀。
參考:
進(jìn)程與線程的一個(gè)簡(jiǎn)單解釋 -- 阮大大
【譯】JavaScript 如何工作的: 事件循環(huán)和異步編程的崛起 + 5 個(gè)關(guān)于如何使用 async/await 編寫(xiě)更好的技巧
已同步至個(gè)人博客-軟硬皆施
Github 歡迎star :)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93410.html
摘要:刨根問(wèn)底,這里說(shuō)的成本,到底高在哪兒呢什么是文檔對(duì)象模型什么是可能很多人第一反應(yīng)就是等標(biāo)簽至少我是,但要知道,是,是,對(duì)象模型,是為提供的。操作具體的成本,說(shuō)到底是造成瀏覽器回流和重繪,從而消耗資源。 從我接觸前端到現(xiàn)在,一直聽(tīng)到的一句話:操作DOM的成本很高,不要輕易去操作DOM。尤其是React、vue等MV*框架的出現(xiàn),數(shù)據(jù)驅(qū)動(dòng)視圖的模式越發(fā)深入人心,jQuery時(shí)代提供的強(qiáng)大便...
摘要:刨根問(wèn)底,這里說(shuō)的成本,到底高在哪兒呢什么是文檔對(duì)象模型什么是可能很多人第一反應(yīng)就是等標(biāo)簽至少我是,但要知道,是,是,對(duì)象模型,是為提供的。操作具體的成本,說(shuō)到底是造成瀏覽器回流和重繪,從而消耗資源。 從我接觸前端到現(xiàn)在,一直聽(tīng)到的一句話:操作DOM的成本很高,不要輕易去操作DOM。尤其是React、vue等MV*框架的出現(xiàn),數(shù)據(jù)驅(qū)動(dòng)視圖的模式越發(fā)深入人心,jQuery時(shí)代提供的強(qiáng)大便...
摘要:作用域鏈用于表明上下文的執(zhí)行順序。當(dāng)前上下文執(zhí)行完畢則出棧,執(zhí)行下一個(gè)上下文。 從一個(gè)簡(jiǎn)單的例子出發(fā) 先從一個(gè)簡(jiǎn)單的例子出發(fā)(先不涉及異步),看看自己是否大致了解瀏覽器的執(zhí)行機(jī)制: console.log(a); var a=1; function foo(a){ console.log(a); var a=2; console.log(a); } foo(a)...
摘要:瀏覽器創(chuàng)建進(jìn)程的現(xiàn)象如圖所示默認(rèn)的情況下打開(kāi)瀏覽器,會(huì)創(chuàng)建以上進(jìn)程。主要的三個(gè)為瀏覽器進(jìn)程,進(jìn)程,和一個(gè)默念的標(biāo)簽頁(yè)進(jìn)程。當(dāng)我們?yōu)g覽某個(gè)網(wǎng)頁(yè)的時(shí)候,引擎就會(huì)切換到這個(gè)網(wǎng)頁(yè)線程上運(yùn)行。 1.瀏覽器創(chuàng)建進(jìn)程的現(xiàn)象 showImg(https://segmentfault.com/img/bV42yw?w=687&h=370);如圖所示默認(rèn)的情況下打開(kāi)瀏覽器,會(huì)創(chuàng)建以上進(jìn)程。主要的三個(gè)為:瀏...
摘要:瀏覽器的事件循環(huán),前端再熟悉不過(guò)了,每天都會(huì)接觸的東西。可以看到,所謂的并不是瀏覽器定義了哪些任務(wù)是,瀏覽器各個(gè)線程只是忠實(shí)地循環(huán)自己的任務(wù)隊(duì)列,不停地執(zhí)行其中的任務(wù)而已。 瀏覽器的事件循環(huán),前端再熟悉不過(guò)了,每天都會(huì)接觸的東西。但我以前一直都是死記硬背:事件任務(wù)隊(duì)列分為macrotask和microtask,瀏覽器先從macrotask取出一個(gè)任務(wù)執(zhí)行,再執(zhí)行microtask內(nèi)的所...
閱讀 1632·2021-11-22 13:53
閱讀 2874·2021-11-15 18:10
閱讀 2776·2021-09-23 11:21
閱讀 2518·2019-08-30 15:55
閱讀 492·2019-08-30 13:02
閱讀 769·2019-08-29 17:22
閱讀 1714·2019-08-29 13:56
閱讀 3467·2019-08-29 11:31