亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

JS是單線程,你了解其運行機制嗎?

AlphaGooo / 3102人閱讀

摘要:的單線程,與它的用途有關。事件循環(huán)事件循環(huán)是指主線程重復從消息隊列中取消息執(zhí)行的過程。到此為止,就完成了工作線程對主線程的通知,回調(diào)函數(shù)也就得到了執(zhí)行。

一. 區(qū)分進程和線程

很多新手是區(qū)分不清線程和進程的,沒有關系。這很正常。先看看下面這個形象的比喻:

進程是一個工廠,工廠有它的獨立資源-工廠之間相互獨立-線程是工廠中的工人,多個工人協(xié)作完成任務-工廠內(nèi)有一個或多個工人-工人之間共享空間

如果是windows電腦中,可以打開任務管理器,可以看到有一個后臺進程列表。對,那里就是查看進程的地方,而且可以看到每個進程的內(nèi)存資源信息以及cpu占有率。

所以,應該更容易理解了:進程是cpu資源分配的最小單位(系統(tǒng)會給它分配內(nèi)存)

最后,再用較為官方的術語描述一遍:

進程是cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位)

線程是cpu調(diào)度的最小單位(線程是建立在進程的基礎上的一次程序運行單位,一個進程中可以有多個線程)

提示:

不同進程之間也可以通信,不過代價較大

現(xiàn)在,一般通用的叫法:單線程與多線程,都是指在一個進程內(nèi)的單和多。(所以核心還是得屬于一個進程才行)

二. 瀏覽器是多進程的

理解了進程與線程了區(qū)別后,接下來對瀏覽器進行一定程度上的認識:(先看下簡化理解)

瀏覽器是多進程的

瀏覽器之所以能夠運行,是因為系統(tǒng)給它的進程分配了資源(cpu、內(nèi)存)

簡單點理解,每打開一個Tab頁,就相當于創(chuàng)建了一個獨立的瀏覽器進程。

關于以上幾點的驗證,請再第一張圖:

圖中打開了Chrome瀏覽器的多個標簽頁,然后可以在Chrome的任務管理器中看到有多個進程(分別是每一個Tab頁面有一個獨立的進程,以及一個主進程)。

感興趣的可以自行嘗試下,如果再多打開一個Tab頁,進程正常會+1以上(不過,某些版本的ie卻是單進程的)

注意:在這里瀏覽器應該也有自己的優(yōu)化機制,有時候打開多個tab頁后,可以在Chrome任務管理器中看到,有些進程被合并了(所以每一個Tab標簽對應一個進程并不一定是絕對的)

三、為什么JavaScript是單線程?

JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。那么,為什么JavaScript不能有多個線程呢?這樣能提高效率啊。

JavaScript的單線程,與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內(nèi)容,另一個線程刪除了這個節(jié)點,這時瀏覽器應該以哪個線程為準?

所以,為了避免復雜性,從一誕生,JavaScript就是單線程,這已經(jīng)成了這門語言的核心特征,將來也不會改變。

為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質(zhì)。

四. JavaScript是單線程,怎樣執(zhí)行異步的代碼?

單線程就意味著,所有任務需要排隊,前一個任務結(jié)束,才會執(zhí)行后一個任務。如果前一個任務耗時很長,后一個任務就不得不一直等著。
js引擎執(zhí)行異步代碼而不用等待,是因有為有 消息隊列和事件循環(huán)。

消息隊列:消息隊列是一個先進先出的隊列,它里面存放著各種消息。
事件循環(huán):事件循環(huán)是指主線程重復從消息隊列中取消息、執(zhí)行的過程。

實際上,主線程只會做一件事情,就是從消息隊列里面取消息、執(zhí)行消息,再取消息、再執(zhí)行。當消息隊列為空時,就會等待直到消息隊列變成非空。而且主線程只有在將當前的消息執(zhí)行完成后,才會去取下一個消息。這種機制就叫做事件循環(huán)機制,取一個消息并執(zhí)行的過程叫做一次循環(huán)。

事件循環(huán)用代碼表示大概是這樣的:

while(true) {
 ? ?var message = queue.get();
 ? ?execute(message);
}

那么,消息隊列中放的消息具體是什么東西?消息的具體結(jié)構當然跟具體的實現(xiàn)有關,但是為了簡單起見,我們可以認為:

消息就是注冊異步任務時添加的回調(diào)函數(shù)。

再次以異步AJAX為例,假設存在如下的代碼:

$.ajax("http://segmentfault.com", function(resp) {
 ? ?console.log("我是響應:", resp);
});

// 其他代碼
...
...
...

主線程在發(fā)起AJAX請求后,會繼續(xù)執(zhí)行其他代碼。AJAX線程負責請求segmentfault.com,拿到響應后,它會把響應封裝成一個JavaScript對象,然后構造一條消息:

// 消息隊列中的消息就長這個樣子
var message = function () {
 ? ?callbackFn(response);
}

其中的callbackFn就是前面代碼中得到成功響應時的回調(diào)函數(shù)。

主線程在執(zhí)行完當前循環(huán)中的所有代碼后,就會到消息隊列取出這條消息(也就是message函數(shù)),并執(zhí)行它。到此為止,就完成了工作線程對主線程的通知,回調(diào)函數(shù)也就得到了執(zhí)行。如果一開始主線程就沒有提供回調(diào)函數(shù),AJAX線程在收到HTTP響應后,也就沒必要通知主線程,從而也沒必要往消息隊列放消息。

用圖表示這個過程就是:

從上文中我們也可以得到這樣一個明顯的結(jié)論,就是:

異步過程的回調(diào)函數(shù),一定不在當前這一輪事件循環(huán)中執(zhí)行。
事件循環(huán)進階:macrotask與microtask

一張圖展示JavaScript中的事件循環(huán):

一次事件循環(huán):先運行macroTask隊列中的一個,然后運行microTask隊列中的所有任務。接著開始下一次循環(huán)(只是針對macroTask和microTask,一次完整的事件循環(huán)會比這個復雜的多)。

JS中分為兩種任務類型:macrotask和microtask,在ECMAScript中,microtask稱為jobs,macrotask可稱為task

它們的定義?區(qū)別?簡單點可以按如下理解:

macrotask(又稱之為宏任務),可以理解是每次執(zhí)行棧執(zhí)行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調(diào)并放到執(zhí)行棧中執(zhí)行)

每一個task會從頭到尾將這個任務執(zhí)行完畢,不會執(zhí)行其它

瀏覽器為了能夠使得JS內(nèi)部task與DOM任務能夠有序的執(zhí)行,會在一個task執(zhí)行結(jié)束后,在下一個 task 執(zhí)行開始前,對頁面進行重新渲染
(task->渲染->task->...)

microtask(又稱為微任務),可以理解是在當前 task 執(zhí)行結(jié)束后立即執(zhí)行的任務

也就是說,在當前task任務后,下一個task之前,在渲染之前

所以它的響應速度相比setTimeout(setTimeout是task)會更快,因為無需等渲染

也就是說,在某一個macrotask執(zhí)行完后,就會將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)

分別很么樣的場景會形成macrotask和microtask呢?

macroTask: 主代碼塊, setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering(可以看到,事件隊列中的每一個事件都是一個macrotask)

microTask: process.nextTick, Promise, Object.observe, MutationObserver

補充:在node環(huán)境下,process.nextTick的優(yōu)先級高于Promise,也就是可以簡單理解為:在宏任務結(jié)束后會先執(zhí)行微任務隊列中的nextTickQueue部分,然后才會執(zhí)行微任務中的Promise部分。

另外,setImmediate則是規(guī)定:在下一次Event Loop(宏任務)時觸發(fā)(所以它是屬于優(yōu)先級較高的宏任務),(Node.js文檔中稱,setImmediate指定的回調(diào)函數(shù),總是排在setTimeout前面),所以setImmediate如果嵌套的話,是需要經(jīng)過多個Loop才能完成的,而不會像process.nextTick一樣沒完沒了。

實踐:上代碼

我們以setTimeout、process.nextTick、promise為例直觀感受下兩種任務隊列的運行方式。

console.log("main1");

process.nextTick(function() {
 ? ?console.log("process.nextTick1");
});

setTimeout(function() {
 ? ?console.log("setTimeout");
 ? ?process.nextTick(function() {
 ? ? ? ?console.log("process.nextTick2");
 ? ?});
}, 0);

new Promise(function(resolve, reject) {
 ? ?console.log("promise");
 ? ?resolve();
}).then(function() {
 ? ?console.log("promise then");
});

console.log("main2");

別著急看答案,先以上面的理論自己想想,運行結(jié)果會是啥?

最終結(jié)果是這樣的:

main1
promise
main2
process.nextTick1
promise then
setTimeout
process.nextTick2

process.nextTick 和 promise then在 setTimeout 前面輸出,已經(jīng)證明了macroTask和microTask的執(zhí)行順序。但是有一點必須要指出的是。上面的圖容易給人一個錯覺,就是主進程的代碼執(zhí)行之后,會先調(diào)用macroTask,再調(diào)用microTask,這樣在第一個循環(huán)里一定是macroTask在前,microTask在后。

但是最終的實踐證明:在第一個循環(huán)里,process.nextTick1和promise then這兩個microTask是在setTimeout這個macroTask里之前輸出的,這是為什么呢?

因為主進程的代碼也屬于macroTask(這一點我比較疑惑的是主進程都是一些同步代碼,而macroTask和microTask包含的都是一些異步任務,為啥主進程的代碼會被劃分為macroTask,不過從實踐來看確實是這樣,而且也有理論支撐:【翻譯】Promises/A+規(guī)范)。

主進程這個macroTask(也就是main1、promise和main2)執(zhí)行完了,自然會去執(zhí)行process.nextTick1和promise then這兩個microTask。這是第一個循環(huán)。之后的setTimeout和process.nextTick2屬于第二個循環(huán)

別看上面那段代碼好像特別繞,把原理弄清楚了,都一樣 ~

requestAnimationFrame、Object.observe(已廢棄) 和 MutationObserver這三個任務的運行機制大家可以從上面看到,不同的只是具體用法不同。重點說下UI rendering。在HTML規(guī)范:event-loop-processing-model里敘述了一次事件循環(huán)的處理過程,在處理了macroTask和microTask之后,會進行一次Update the rendering,其中細節(jié)比較多,總的來說會進行一次UI的重新渲染。

事件循環(huán)機制進一步補充

這里就直接引用一張圖片來協(xié)助理解:(參考自Philip Roberts的演講《Help, I’m stuck in an event-loop》)

上圖大致描述就是:

主線程運行時會產(chǎn)生執(zhí)行棧,棧中的代碼調(diào)用某些api時,它們會在事件隊列中添加各種事件(當滿足觸發(fā)條件后,如ajax請求完畢)

而棧中的代碼執(zhí)行完畢,就會讀取事件隊列中的事件,去執(zhí)行那些回調(diào)

如此循環(huán)

注意,總是要等待棧中的代碼執(zhí)行完畢后才會去讀取事件隊列中的事件

五. 最后

看到這里,應該對JS的運行機制有一定的理解了吧。

參考:

http://www.ruanyifeng.com/blo...

https://mp.weixin.qq.com/s/vI...

https://mp.weixin.qq.com/s?__...

https://mp.weixin.qq.com/s/k_...

我不是大神,也不是什么牛人,寫這個號的目的是為了記錄我自學 web全棧 的筆記。

全棧修煉 有興趣的朋友可以掃下方二維碼關注我的公眾號

我會不定期更新有價值的內(nèi)容,長期運營。

關注公眾號并回復 福利 可領取免費學習資料,福利詳情請猛戳: Python、Java、Linux、Go、node、vue、react、javaScript

文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/96378.html

相關文章

  • 10分鐘理解JS引擎的執(zhí)行機制

    摘要:深入理解引擎的執(zhí)行機制靈魂三問為什么是單線程的為什么需要異步單線程又是如何實現(xiàn)異步的呢中的中的說說首先請牢記點是單線程語言的是的執(zhí)行機制。 深入理解JS引擎的執(zhí)行機制 1.靈魂三問 : JS為什么是單線程的? 為什么需要異步? 單線程又是如何實現(xiàn)異步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.說說setTimeout 首先,請牢記2...

    zzbo 評論0 收藏0
  • JS高級入門教程

    摘要:解析首先簡稱是由歐洲計算機制造商協(xié)會制定的標準化腳本程序設計語言。級在年月份成為的提議,由核心與兩個模塊組成。通過引入統(tǒng)一方式載入和保存文檔和文檔驗證方法對進行進一步擴展。其中表示的標記位正好是低三位都是。但提案被拒絕了。 JS高級入門教程 目錄 本文章定位及介紹 JavaScript與ECMAScript的關系 DOM的本質(zhì)及DOM級介紹 JS代碼特性 基本類型與引用類型 JS的垃...

    zsy888 評論0 收藏0
  • JavaScript事件循環(huán)

    摘要:事件循環(huán)當主線程中的任務執(zhí)行完畢后,會從任務隊列中獲取任務一個個的放在棧中執(zhí)行去執(zhí)行,這個過程是循環(huán)不斷的,所以整個的這種運行機制又稱為事件循環(huán)。 寫在前面 說起javascript(以下簡稱js)這門語言,相信大家已經(jīng)非常熟悉了,不管是前端開發(fā)還是后端開發(fā)幾乎無時無刻都要跟它打交道。雖說開發(fā)者每天幾乎都要操作js,但是你真的確定你掌握了js的運行機制嗎!下面我們就來聊聊這話題。 Ja...

    Corwien 評論0 收藏0
  • [譯文] JavaScript工作原理:引擎、運行時、調(diào)用棧概述

    摘要:調(diào)用棧是單線程編程語言,意味著它只有單一的調(diào)用棧。調(diào)用棧是一種數(shù)據(jù)結(jié)構,基本記錄了程序運行的位置。舉個例子,先來看如下所示的代碼當引擎開始執(zhí)行這段代碼時,調(diào)用棧將是空的。這正是拋出異常時棧追蹤的構造過程這基本上就是異常拋出時調(diào)用棧的狀態(tài)。 原文 How JavaScript works: an overview of the engine, the runtime, and the c...

    PAMPANG 評論0 收藏0
  • 探秘JS的異步單線程

    摘要:對于通常的特別是那些具備并行計算多線程背景知識的來講,的異步處理著實稱得上詭異。而這個詭異從結(jié)果上講,是由的單線程這個特性所導致的。的特性之一是單線程,也即是從頭到尾,都在同一根線程下運行。而這兩者的不同,便在于單線程和多線程上。 對于通常的developer(特別是那些具備并行計算/多線程背景知識的developer)來講,js的異步處理著實稱得上詭異。而這個詭異從結(jié)果上講,是由js...

    cooxer 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<