進程是系統資源分配一個獨立單位,一個程序至少有一個進程。比方說:一個工廠表明一個 CPU, 一個車間就是一個進程,任一時刻,只能有一個進程在運行,其餘進程處於非運行狀態。javascript
線程是CPU調度和分派的基本單位,一個線程只能屬於一個進程,一個進程能夠有多個線程且至少有一個。比方說一個車間的工人,能夠有多個工人一塊兒工做。java
生活中經常能看到,某某電腦 CPU 的 4 核 4 線程,其意思是指,這款 CPU 同一時間最多隻能運行 4 個線程,因此有些線程會處於工做狀態,有的線程會處於中斷,堵塞,睡眠狀態。web
常常看到有不少任務同時在進行,一邊工做,一邊聽歌,還一邊下載電影。那是由於這些線程在以閃電般的速度不斷的切換主要的幾個線程,因此,人的體驗上感受是不少不少任務在同時進行。promise
棧是一種數據結構,具備後進先出的特色,最開始進入棧結構的數據反而最後才能出來。 瀏覽器
隊列也是一種數據結構,數據只能從一邊進,一邊出,先進去的天然就先出來。 bash
同步和異步關注的消息通訊機制,同步在函數調用時,若是調用者沒有拿到響應結果,程序會繼續等待,知道拿到結果爲止。而異步會執行其後的代碼,等到有響應結果後,才處理響應。網絡
阻塞和非阻塞關注的是程序等待調用結果時的狀態,阻塞的意思是,在調用結果返回響應前,線程會被掛起佔用,程序沒法繼續往下走,而非阻塞的線程則不會掛起,後面的代碼可以繼續往下執行。數據結構
比方說:我去超市買包薯片,老闆告訴我貨架上沒貨了,立刻去庫房拿,這過程當中,老闆要我站着等他,直到他拿到貨出來給我。這個過程就是阻塞。多線程
若是老闆告訴我,能夠先回去,他一會去庫房拿,拿到了以後打電話給我。這個過程,就是非阻塞的,我不用等待,還能夠幹其餘的事情。閉包
js 代碼在執行代碼時,JS 會給調用代碼生成一個執行上下文對象,並將其壓入執行上下文棧,首先進入棧底的是全局上下文,而後是函數的執行上下文(Execution Context),函數執行完以後,函數上下文從棧中彈出,直到退出瀏覽器,全局上下文才從棧底彈出。
用代碼舉個例子:
var globalName = "window";
var foo1 = function() {
console.log("foo1");
}
var foo2 = function() {
console.log("foo2");
foo1();
}
foo2();
複製代碼
上面的圖片大體可以描述執行上下文棧的實現邏輯,有關執行上下文的知識,你們能夠翻看我以前的文章 - 《JavaScript 之執行上下文》
JavaScript 的一個很是有趣的特性是事件循環模型,與許多其餘語言不一樣,它永不阻塞。 處理 I/O 一般經過事件和回調來執行 -- MDN
瀏覽器主要任務是給用戶是視覺和交互上的體驗,若是頁面使用過程當中,偶爾出現阻塞、掛起、無響應的體驗必定是很是糟糕的。同時,若是採用多線程同步的模型,那麼如何保證同一時間修改了 DOM, 究竟是哪一個線程先生效呢。
瀏覽器執行環境的核心思想在於任務調度方式的特別:
哪一個任務的優先級高,先來就先運行,直到執行完了才執行下一個,而且同一時刻只能執行一個代碼片斷,即所謂的單線程模型。
比方說,銀行的櫃檯只開啓了一個櫃檯,每一個人想要辦理業務,就得先拿號排隊,叫到了你的號碼,你才能上去辦理業務。不能多我的同時在一個櫃檯辦理業務,否則就很容易出差錯。
事件循環是 JS 處理各類事件的核心,因爲多個線程同時操做 DOM, 形成不可控的問題,因此 JS 採用了單線程模型。另外,因爲全部的事件同步執行,執行完一個才能執行下一個,會形成頁面渲染的堵塞。JS 中存在異步事件,用戶能夠在點擊頁面的時候,請求網絡響應的同事,還能夠進行其餘的點擊操做,保證了頁面不會由於網絡請求,多種 IO 接口響應慢形成代碼執行的堵塞和掛起。
事件循環的順序是:
一個
事件出隊,而後壓入執行棧中執行。接下來咱們用代碼來解釋:
console.log("script start!");
function foo1() {
console.log("foo1");
}
foo1();
setTimeout(function () {
console.log("setTimeout!");
}, 1000);
function foo2() {
console.log("foo2");
}
foo2();
console.log("script end!");
打印:
// script start!
// foo1
// foo2
// script end!
// setTimeout!
複製代碼
那咱們嘗試把 setTimeout 的延遲時間改成 0,想要當即執行,看會不會當即執行:
console.log("script start!");
function foo1() {
console.log("foo1");
}
foo1();
setTimeout(function () {
console.log("setTimeout!");
}, 0);
function foo2() {
console.log("foo2");
}
foo2();
console.log("script end!");
打印:
// script start!
// foo1
// foo2
// script end!
// setTimeout!
複製代碼
能夠看出 setTimeout 屬於異步事件,老是會在主線程的任務執行完後纔開始執行。
順便說一下事件循環幾個原則:
- 一次只處理一個任務
- 一個任務從開始到完成,不會被其餘任務所中斷
這兩個原則保證了瀏覽器任務單元的完整性,事件調用的有序性。
事件循環的實現原本應該由一個用於宏任務的隊列和一個用於微任務的隊列進行完成,這使得事件循環要根據任務類型來進行優先處理。
宏任務:
宏任務包括:
宏任務表明一個個離散、獨立的工做單元,運行完任務後,瀏覽器能夠進行其餘的任務調度,如更新渲染或執行垃圾回收。宏任務須要屢次事件循環才能執行完。
微任務:
微任務包括:
微任務是更小的任務,微任務須要儘量地、經過異步方式執行,微任務更新瀏覽器的狀態,但必須在瀏覽器執行其餘任務以前執行。微任務使得咱們避免沒必要要的 UI 重繪。微任務在一次事件循環中必須所有執行完。
宏任務和微任務的執行優先級原則是:
完成一個宏任務後,執行餘下的微任務
同一次事件循環中,宏任務永遠在微任務以前執行。
ok,知道了優先級原則後,咱們來看一段代碼:
console.log(1);
setTimeout(function() {
console.log(2);
new Promise(resolve => {
console.log(3);
resolve(4);
console.log(5);
}).then(data => {
console.log(data);
});
}, 0);
new Promise(resolve => {
console.log(6);
resolve(7);
console.log(8);
}).then(data => {
console.log(data);
});
setTimeout(function() {
console.log(9);
}, 0);
console.log(10);
output:
第一次循環:
// 1
// 6
// 8
// 10
// 7
第二次循環:
// 2
// 3
// 5
// 4
第三次循環
// 9
複製代碼
咱們一塊兒來分析以上代碼:
關於事件循環宏任務和微任務的執行過程:
- 首先兩個類型的任務都是逐個執行
- 微任務會前下一個渲染或垃圾回收前所有執行完
- 一次事件循環中先只執行一個宏任務,在下一次事件循環前執行完全部的微任務,包括新建立的微任務。
儘管 HTML5 新標準加入了 web worker 的多線程技術,可是 web worker 只能用於計算,而且 JS 的多線程 worker 沒法操做 DOM, 否則就沒法控制頁面是在被誰操做的了。
主線程傳給子線程的數據是經過拷貝複製,一樣子線程傳給主線程的數據也是經過拷貝複製,而不是共享同一個內存空間。
以上說明,JS 不存在線程同步,因此仍是能夠把 JS 看作單線程模型,把 web worker 當作 JS 的一種回調機制。
事件循環是 JS 和 Nodejs 事件調用機制的核心,保證了頁面能夠有序無阻塞的進行。
事件循環的主要邏輯是先執行調用棧,直到清空調用棧只剩下全局上下文。
而後 JS 檢查宏任務隊列,若是有任務則取出一個進行調用,進行頁面渲染和垃圾回收。
同時將全部的微任務源派發的任務加入微任務事件隊列,最後執行餘下的全部微任務。微任務執行後完,進行頁面渲染和垃圾回收後進行下一輪事件循環。
歡迎關注個人我的公衆號「謝南波」,專一分享原創文章。