js單線程 js是單線程的,這樣更有利與用戶交互以及DOM操作;有關進程與線程的詳細解釋可以點擊傳送門;儘管webworker可以實現多線程,但本質上他還屬於單線程,由webworker創建的線程都由主線程式控制制並且只能進行計算而已; js同步、非同步 同步執行:即js主線程按順序執行任務,如遇操作we ...
js單線程
js是單線程的,這樣更有利與用戶交互以及DOM操作;有關進程與線程的詳細解釋可以點擊傳送門;儘管webworker可以實現多線程,但本質上他還屬於單線程,由webworker創建的線程都由主線程式控制制並且只能進行計算而已;
js同步、非同步
同步執行:即js主線程按順序執行任務,如遇操作webAPI/ajax等代碼時會等待其響應後面代碼得不到執行,也就是下一個任務必須等到前一個任務執行完成;
非同步執行:js是單線程的本身不具備非同步能力,但瀏覽器可以;當js執行時遇到webAPI(如setTimeout/ajax等),會立即返回一個值,從而不阻塞主線程,而真正的非同步由瀏覽器執行,待完成後講其回調函數推入js主線程的消息隊列中等待主線程調用;
事件迴圈機制[Event-loop]
js執行時其主線程擁有一個執行棧[個人習慣叫調用棧](stack)和一個消息列隊[也叫任務列隊或事件列隊](Event-queue);當js執行時遇到函數function時會將其入棧待函數執行完在出棧,主線程調用執行棧並執行,如果遇到webAPI會非同步執行;當執行棧中沒有任務時,主線程會查詢消息列隊,如查詢成功,則將查詢的任務入棧執行,直到執行棧為空,再次查詢消息列隊,這樣形成一個迴圈就是大名鼎鼎的事件迴圈[Event-loop];由於非同步執行是瀏覽器完成的這也就好理解為什麼js線程阻塞時操作dom事件會線上程恢復後依次執行,[js主線程的任務列隊是瀏覽器推入的,js線程阻塞!==瀏覽器線程阻塞,換言之即是主線程阻塞也不妨礙向任務列隊推入任務];畫個草圖;
再來段代碼:
// 主線程執行fn1任務 1 function fn1(){ console.log("任務1執行") // 遇到webAPI立即返回 這裡是undefined值 並交給瀏覽器對應線程處理 2 // 瀏覽器收到後 0 毫秒將回調函數推入消息列隊; 非同步執行 setTimeout(function(){// 查詢到一個回調任務 入棧執行 5 console.log("回調1執行") // 遇到webAPI立即返回 這裡是undefined值 並交給瀏覽器對應線程處理 6 // 瀏覽器收到後 500 毫秒將回調函數推入消息列隊; 非同步執行 setTimeout(function(){// 查詢到一個回調任務 入棧執行 7 console.log("回調2執行") },500) },0) } // 主線程執行fn2任務 3 function fn2(){console.log("任務2執行")} // 執行棧沒有可執行任務 開始查詢消息列隊 4
Macrotasks 和 Microtasks
消息列隊分為兩種即Macrotasks[Task Queue]與Microtasks[Microtasks Queue ];
- macrotasks:
setTimeout
,setInterval
,setImmediate
, I/O, UI rendering -
microtasks:
process.nextTick
,Promises
,Object.
MutationObserver
這就引發一個問題他倆到底該誰先執行呢?Promise/A+規範指出:
Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called. 其實執行過程也是很好理解的也是一個迴圈查詢,在前面的基礎上查詢消息列隊時 會先查詢 microtasks將其所有任務執行完畢後在查詢macrotasks如果執行macrotasks中有microtasks任務則下次查詢依舊會先執行完microtasks列隊任務在查詢macrotasks 如此反覆 直到消息列隊[兩個 Queue]無任務; ok我們上段代碼[promise特性為es6 故用node環境]console.log(1); setTimeout(function(){ console.log(7) },0); setTimeout(function(){ new Promise(function(resolve){ resolve(); }).then(function(){ console.log(8) }).then(function(){ console.log(9) }).then(function(){ console.log(10); }) },0); new Promise(function(resolve){ resolve(); }).then(function(){ console.log(3) }).then(function(){ console.log(4) }).then(function(){ console.log(5) }).then(function(){ console.log(6) }) console.log(2);
// 1 2 3 4 5 6 7 8 9 10
上邊代碼執行過程:
js執行console 1 遇到setTimeout改為非同步執行,又遇setTimeout再次非同步執行,接著執行遇到promise 被推入microtasks型任務列隊之後執行console 2 至此執行棧為空開始查詢消息列隊,先去查詢microtasks發現有個 有任務可以執行,接著會將任務入棧執行並相應列印 3 4 5 6【只要發現microtasks不為空就執行到為空為止】; 再次查詢消息列隊 此時microtasks列隊已無任務可執行,接著查詢macrotasks列隊發現一個setTimeout回調等待執行,接著入棧執行並出棧,列印 7 ;再次查詢列隊 此時microtasks列隊依然無任務,接著查詢macrotasks列隊又發現一個setTimeou回調等待執行,入棧執行 發現promise推入microtasks列隊 出棧,執行棧又空了。查詢消息列隊 此時發現microtasks有任務可以執行,入棧 執行 出棧;列印響應 8 9 10;至此執行結束主線程處於等待狀態;