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