JavaScript代碼運行分爲兩個階段:javascript
全部函數定義提早,函數體提高(固然不包括如var box = function() {} )
形參聲明並賦值
變量聲明(不賦值)前端
按照js運行機制從,從上到下執行java
舉例:此處有多個工廠,每一個工廠有1個或多個工人。此時工廠就比如進程,有單獨專屬本身的工廠資源;工人就比如是線程,多個工人在工廠中寫做工做。工廠的空間是工人們共享的,這象徵一個進程的內存空間是共享的,每一個線程均可以共享內存。而且每一個工廠之間相互獨立存在。node
瀏覽器內核是指支持瀏覽器運行的最核心的部分,分爲渲染引擎和JS引擎。如今JS引擎比較獨立,內核更加傾向於說渲染引擎ios
(1)瀏覽器內核分類git
(2)瀏覽器進程github
瀏覽器進程的組成:ajax
瀏覽器的主進程,負責協調、主控,只有一個。
負責內容:瀏覽器頁面顯示;與用戶交互(前進、後退等);網絡資源的管理、下載;各個頁面的管理,建立和銷燬其餘進程等axios
瀏覽器是多線程的優點:避免單個Tab頁崩潰或單個插件崩潰影響其餘整個瀏覽器,能夠充分多核優點,方便使用沙盒模型隔離插件等進程,提升瀏覽器的穩定性。缺點是,內存和cpu消耗會更大,有點空間換時間的意思。segmentfault
Borwser進程與瀏覽器內核(Renderer進程)的通訊過程:
Browser進程收到用戶請求,首先須要獲取頁面內容(譬如經過網絡下載資源),隨後將該任務經過RendererHost接口傳遞給Render進程
對於前端操做來講 ,最重要的是渲染進程,而且渲染進程也是多線程的。
渲染進程包含哪些線程?
GUI渲染線程
JS引擎線程
事件觸發線程
好比setTimeout定時器計數結束、ajax等異步請求成功並觸發回調函數、用戶觸發點擊事件等,該線程會將整裝待發的事件加入到任務隊列的隊尾,等待JS引擎線程的執行。
定時器觸發線程
主線程依次執行代碼時,遇到定時器,會將定時器交給該線程處理。當計數完畢後,事件觸發線程會將計數完畢的事件加入到任務隊列的尾部,等待JS引擎線程執行。
異步HTTP請求線程
主線程依次執行代碼是,遇到異步請求,會將異步請求函數交給該線程處理。當監聽到狀態碼變動,若是有回調函數,事件觸發線程會將回調函數加入到任務隊列的尾部,等待 JS引擎線程執行。
JavaScript語言是單線程的,意思是同一時間只能作一件事。後來爲了有效利用多核CPU的計算能力,HTML5提出Web Server標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,而且子線程不能操做DOM。因此新標準並無改變JavaScript單線程的本質。
簡單描述JS的執行機制:
以上四步循環執行,就是event loop。
一個完整的Event Loop過程:
① 全部的同步任務都在主線程上執行,造成一個執行棧(exection context stack),咱們能夠認爲執行棧是一個函數調用的棧結構,遵循先進後出的原則。除了主線程的執行棧,還存在一個任務隊列(task queue),任務隊列分爲宏任務隊列(macro-task queue)和微任務隊列(micro-task queue)。
一開始執行棧爲空,宏任務隊列(macro-task queue)裏只有一個script代碼(總體代碼),微任務隊列(micro-task queue)隊列爲空。
② 宏任務隊列(macro-task queue)中的全局上下文(script標籤)會被推入執行棧,同步代碼執行。在執行的過程當中會判斷是同步任務仍是異步任務,同步任務依次執行,異步任務會經過對一些接口的調用而產生新的macro-task和micro-task(只要異步任務有了運行結果,就會在對應的任務隊列中放置一個事件,等待調用)。同步代碼執行完了,script腳本會行和出隊的過程。
③ 上一步出隊的是一個macro-task,這一步要處理的是micro-task。須要注意的是,當macro-task出隊時,任務是一個一個執行的,而micro-task出隊時,任務是一隊一隊執行的。所以,咱們處理micro-task這一步,會逐個執行隊列中的任務並把它出隊,直到隊列被清空。
④ 執行渲染操做,更新頁面
⑤ 檢查是否存在Web worker任務,若是有,則對其進行進行處理
⑥ 上述過程重複循環,直到兩個隊列都清空
宏任務隊列能夠有多個,而微任務隊列只有一個:
Node中的事件循環與瀏覽器的是徹底不一樣不一樣的東西。Node採用V8做爲js的解析引擎,而I/O處理方面使用本身設計的libuv。
libuv是一個基於事件驅動的跨平臺抽象層,封裝了不一樣操做系統的一些底層特性,對外提供API,事件循環也是在它裏面實現:
NodeJS運行機制以下:
libuv引擎的事件循環分爲6個階段:
絕大部分的異步任務都在timers、poll、check這個3個階段處理
NodeJS執行環境下的特殊狀況:
1)setTimeout和setImmediate
兩者很是類似,區別主要在於調用時機不一樣:
setTimeout(function timeout () { console.log('timeout'); },0); setImmediate(function immediate () { console.log('immediate'); });
對於以上代碼,setTimeout可能執行在前,也可能執行在後;
取決於setImmediate的準備時間;由於當setTimeout指定時間小於4ms,則增長到4ms(4ms是H5de新標準,2010年之前的瀏覽器是10ms)
可是若是兩者在I/O callback內部回調時,老是先執行setImmediate,後執行setTimeout:
const fs = require('fs') fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0) setImmediate(() => { console.log('immediate') }) }) // immediate // timeout // 由於這兩個代碼都寫在I/O回調中,I/O回調是在poll階段執行,當回調執行完畢後隊列清空,發現SetImmediate回調,因此當即跳轉到check階段執行回調。 });
2)process.nextTick
process.nextTick是獨立於Event Loop以外的,它有一個本身的隊列,會優先於其餘micro-task隊列執行:
setTimeout(() => { console.log('timer1') Promise.resolve().then(function() { console.log('promise1') }) }, 0) process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') }) }) }) }) // nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1
瀏覽器環境下,micro-task的任務隊列是每一個macro-task執行以後執行;
Node環境下,在node10及其之前版本,micro-task會在事件循環的各個階段之間執行,也就是一個階段執行完畢,就會執行micro-task隊列的任務
Node在node11版本開始,Event Loop的運行原來發生了變化,一旦一個階段裏的宏任務執行完,就會當即執行微任務隊列,這一點與瀏覽器一直。
因爲JS是單線程,當遇到計算密集型或高延遲的任務,用戶界面可能會短暫「凍結」,不能作其餘操做。
因而HTML5提出Web Worker,它容許JavaScript創造多線程環境,容許主線程建立Worker線程,將一些任務分配給後者。主線程運行的同時,Worker線程在後臺運行,二者互不干擾,等到Worker完成計算任務,在把結果返回給主線程。
Web Worker的優勢是能夠承擔一些密集型或高延遲任務,使主線程流暢,不被阻塞或拖慢。
缺點:
Web Worker使用方法:
主線程調用Worker線程:
// 主線程: var input = document.getElementById('number') document.getElementById('btn').onclick = function () { var number = input.value //一、建立一個Worker對象 var worker = new Worker('worker.js') // 三、綁定接收消息的監聽 worker.onmessage = function (event) { console.log('主線程接收分線程返回的數據: '+event.data) alert(event.data) } // 二、向分線程發送消息 worker.postMessage(number) console.log('主線程向分線程發送數據: '+number) } console.log(this) // window
Worker線程響應:
//worker.js文件 function fibonacci(n) { return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2) //遞歸調用 } console.log(this)//[object DedicatedWorkerGlobalScope] this.onmessage = function (event) { var number = event.data console.log('分線程接收到主線程發送的數據: '+number) //計算 var result = fibonacci(number) postMessage(result) console.log('分線程向主線程返回數據: '+result) // alert(result) alert是window的方法, 在分線程不能調用 // 分線程中的全局對象再也不是window, 因此在分線程中不可能更新界面 }
參考資料:
https://github.com/ljianshu/B...
https://juejin.im/post/5bb054...
深刻淺出JavaScript運行機制
10分鐘理解JS引擎的執行機制
瀏覽器組成
全面梳理JS引擎的運行機制