JS基礎(chǔ)進階- 同步異步編程和EventLoop底層機制
簡短的概括:
- JS中的單線程異步編程
- EventLoop 事件循環(huán)機制
- 宏任務 macrotask [ˈmækroʊ]
- 微任務 microtask [ˈmaɪkroʊ]
- JS中的同步異步編程
1、JS是單線程的,怎么達到異步編程:
1)JS是單線程的,大部分代碼都是同步編程。
2)JS中利用瀏覽器的多線程機制,基于單線程的EventLoop(事件循環(huán)機制)實現(xiàn)出異步的效果。
2、event loop (微任務與宏任務):
1)微任務 (microtask):優(yōu)先級高,并且可以插隊,不是先定義先執(zhí)行。包括:promise.then,async/await [generator],requestAnimationFrame,observer,MutationObserver,setImmediate。
2)宏任務 (macrotask):優(yōu)先級低,先定義的先執(zhí)行。包括:ajax,setTimeout,setInterval,事件綁定,postMessage,MessageChannel(用于消息通訊)。
3、根據(jù)事件循環(huán)機制,重新梳理一下流程:
+先找微任務隊列,如果微任務隊列中有,先從微任務隊列中,一般按照存放順序獲取并且去執(zhí)行。
+如果微任務隊列中沒有,則再去宏任務隊列中查找,在宏任務隊列中,一般是按照誰先到達執(zhí)行的條件,就先把誰拿出來執(zhí)行。
常考面試題:EventLoop事件循環(huán)
🌰 面試題1:
- console.log('1') // 1
- async function async1() {
- console.log('2') // 2
- await setTimeout(() => {
- console.log('3') // 8
- }, 0)
- console.log('4') // 5
- }
- setTimeout(() => {
- console.log('5') // 7
- }, 0)
- async1()
- new Promise(function (resolve) {
- console.log('6') // 3
- resolve()
- }).then(function () {
- console.log('7') // 6
- })
- console.log('8') // 4
- // 結(jié)果:1 2 6 8 4 7 5 3
🌰 面試題2:
- async function async1() {
- console.log('async1 start'); // 2
- await async2();
- console.log('async1 end'); // 6
- }
- async function async2() {
- console.log('async2'); // 3
- }
- console.log('script start'); // 1
- setTimeout(function () {
- console.log('setTimeout'); // 8
- }, 0)
- async1();
- new Promise(function (resolve) {
- console.log('promise1'); // 4
- resolve();
- }).then(function () {
- console.log('promise2'); // 7
- });
- console.log('script end'); // 5
- //結(jié)果:
- script start
- async1 start
- async2
- promise1
- script end
- async1 end
- promise2
- setTimeout
🌰 面試題3:
- console.log(1); // 1
- setTimeout(() => {
- console.log(2); // 6
- Promise.resolve().then(data => {
- console.log(3); // 7
- });
- });
- new Promise((resolve) => {
- resolve()
- console.log(4) // 2
- }).then(() => {
- console.log(5); // 4
- setTimeout(() => {
- console.log(6); // 8
- });
- }).then(() => console.log(7)) // 5
- console.log(8); // 3
- // 結(jié)果:1, 4, 8, 5, 7, 2, 3, 6
- 1, 4, 8 是同步 5, 7 是微任務 2 宏任務 3 微任務 6 宏任務
進程/線程
* 核心答案 | 基礎(chǔ)知識要夯實
1) 進程代表的是一個程序(瀏覽器開一個頁卡 (Tab頁) 就是一個進程);
2) 線程是用來處理處理進程中的具體事物的,如果一個程序中需要同時做好多事情,就需要開辟好多線程;
3) 一個線程同時只能做一件事情;
- 官方的說法
- 1) 進程是cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位)。
- 2) 線程是cpu調(diào)度的最小單位(線程是建立在進程的基礎(chǔ)上的一次程序運行單位,一個進程中可以有多個線程)。
瀏覽器是多線程的
* 核心答案 | 基礎(chǔ)知識要夯實
1) 瀏覽器是多進程的;
2) 瀏覽器之所以能夠運行,是因為系統(tǒng)給它的進程分配了資源(cpu、內(nèi)存);
3) 簡單點理解,每打開一個Tab頁,就相當于創(chuàng)建了一個獨立的瀏覽器進程;

那么接下來看看它都包含了哪些線程(列舉一些主要常駐線程)
GUI渲染線程
1) 負責渲染瀏覽器界面,解析HTML,CSS,構(gòu)建DOM樹和RenderObject樹,布局和繪制等。
2) 當界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時,該線程就會執(zhí)行。
3) 注意,GUI渲染線程與JS引擎線程是互斥的,當JS引擎執(zhí)行時GUI線程會被掛起(相當于被凍結(jié)了),GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執(zhí)行。
JS引擎線程
也稱為JS內(nèi)核,負責處理Javascript腳本程序。(例如V8引擎)
1) JS引擎線程負責解析Javascript腳本,運行代碼。
2) JS引擎一直等待著任務隊列中任務的到來,然后加以處理,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序。
3) 同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執(zhí)行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞。
JS中的單線程異步編程
* 核心答案 | 基礎(chǔ)知識要夯實
JS是單線程的:瀏覽器只分配一個線程用來渲染JS代碼。
1、JS中的代碼大部分都是“同步編程”:上面的任務沒有處理完成,下面的任務是無法處理的。
2、但是JS中利用瀏覽器的多線程機制,可以規(guī)劃出“異步編程”效果。
- 定時器
- ajax/Fetch/跨域 (HTTP網(wǎng)絡(luò)請求)
- 事件綁定
- Promise中有也有異步編程
- Generator / yield
- async / await
計算程序執(zhí)行的時間(預估)
1)運行監(jiān)控 console.time/timeEnd(受當前電腦運行環(huán)境的影響)
2)大O表示法(提前預估)
- console.time('AAA');
- for (let i = 0; i < 99999999; i++) {}
- console.timeEnd('AAA');
真實項目中應該避免死循環(huán) (重要)
- while (true) {}
- console.log('OK'); // 不執(zhí)行:上述的死循環(huán)一直占用這“JS渲染線程”,線程空閑不下來,就處理不了其他的事情
定時器的異步編程
1)設(shè)置定時器任務是同步的
2)“間隔interval這么長時間,執(zhí)行定時器綁定的函數(shù)” 這個任務是異步的
3)遇到異步任務,瀏覽器不會等待它執(zhí)行完,則繼續(xù)渲染下面的代碼;當?shù)鹊较旅娲a運行完,時間也到達了執(zhí)行的條件,才會把異步任務執(zhí)行;
- setTimeout(() => {
- console.log("OK"); //2
- }, 1000);
- console.log('NO'); //1
interval設(shè)置為零也不是立即執(zhí)行,而是瀏覽器都有“最快反應時間(谷歌:5~6ms IE:13~17ms)”,設(shè)置為零,最快也需要等到5~6ms左右
- setTimeout(() => {
- console.log('OK'); //2
- }, 0);
- console.log('NO'); //1
🌰 異步編程例子一:
- setTimeout(() => {
- console.log(1);
- }, 20);
- console.log(2);
- setTimeout(() => {
- console.log(3);
- }, 10);
- console.log(4);
- console.time('AA');
- for (let i = 0; i < 90000000; i++) {
- // do soming
- }
- console.timeEnd('AA'); //=>AA: 79ms 左右
- console.log(5);
- setTimeout(() => {
- console.log(6);
- }, 8);
- console.log(7);
- setTimeout(() => {
- console.log(8);
- }, 15);
- console.log(9);
- // 結(jié)果:2,4,5,7,9,3,1,6,8
畫圖分析:( 有圖有真相 )

執(zhí)行順序:同步任務 —> 微任務 —> 宏任務 (微任務、宏任務在EventQueue)
- 細節(jié)點
- 當棧中的“同步任務”或者其它任務沒有執(zhí)行完之前,JS渲染線程不會空閑下來,些時哪怕定時器已經(jīng)到達指定時間,也不會執(zhí)行的。 “JS是單線程的,一次只能做一件事情” => 定時器設(shè)定的等待時間是最快觸發(fā)執(zhí)行的時間,很多時候,到時間不一定會執(zhí)行,只有JS渲染線程空閑下來才會執(zhí)行。






