你們很早就知道JS是一門單線程的語言。可是也時不時的會看到進程這個詞。首先簡單區分下線程和進程的概念javascript
- 進程是一個工廠,工廠有它的獨立資源 - 工廠之間相互獨立 - 線程是工廠中的工人,多個工人協做完成任務 - 工廠內有一個或多個工人 - 工人之間共享空間
- 工廠的資源 -> 系統分配的內存(獨立的一塊內存) - 工廠之間的相互獨立 -> 進程之間相互獨立 - 多個工人協做完成任務 -> 多個線程在進程中協做完成任務 - 工廠內有一個或多個工人 -> 一個進程由一個或多個線程組成 - 工人之間共享空間 -> 同一進程下的各個線程之間共享程序的內存空間(包括代碼段、數據集、堆等)
上面的1.1和1.2可能仍是有些抽象。接下來用與前端息息相關的瀏覽器爲例展開。html
當你打開瀏覽器開了好幾個網頁的時候,打開瀏覽器的任務管理器(好比谷歌瀏覽器-> 更多工具 -> 任務管理器)
這裏就是查看進程的地方,並且能夠看到每一個進程的cpu佔用率和內存資源信息。前端
簡單用比較官方的術語總結下:html5
在瀏覽器中打開一個網頁至關於新起了一個進程,每一個進程內又會有本身的多線程(固然,瀏覽器有自身的優化機制,當你開了不少空的標籤頁的時候,可能會發現多個空白標籤頁被合併成了一個進程)。好比頁面的渲染,JS的執行,事件的循環,都會在這個進程內進行。(如下用比較官方的術語列舉一些主要常駐線程)java
擴散思考1:瀏覽器爲何要弄成多進程的?jquery
優勢:ajax
- 避免單個標籤頁崩潰影響整個瀏覽器
- 避免第三方插件崩潰影響整個瀏覽器
- 多進程充分利用多核優點
- 方便使用沙盒模型隔離插件等進程,提升瀏覽器穩定性
缺點:segmentfault
- 會佔用更多的內存
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> .a { width: 100px; height: 100px; background: #f60; } </style> <script> console.time('js執行') for(var i = 0; i < 1000000000; i++) { } console.timeEnd('js執行') </script> </head> <body> <div class="a">a</div> </body> </html>
從這個例子中能夠看到JS頁面明顯有一段空白期,也就證實了上面所說的當JS引擎執行時GUI線程會被掛起。promise
擴展思考:你可能之前據說而且一直是這麼作的,JS調用不放在中,要放到網頁底部前面來優化你的網站。可是修改這個例子可能會發現不管你是將這段script包含的代碼放到head裏仍是body裏,或者是另外新建一個文件引入,都要等到js加載而且執行纔會在頁面裏渲染出a。尤爲是jquery時代你們統一會將代碼寫在$(document).ready中,那樣的話JS無論在頂部引入仍是在底部引入,這樣看起來它們的執行時機對頁面的影響是同樣的,那JS調用放在頂部和底部真的會有差異嗎?瀏覽器
推薦閱讀:網站爲何 JS 調用盡可能放到網頁底部?
看了上面的描述後,思考兩個問題。
JS引擎包含兩個部分
內存堆(Memory Heap): 和內存分配有關。(好比基本類型值存棧內存裏,引用類型值存堆內存裏)
調用棧(Call Stack): 代碼執行時候的棧幀 (你可能看到過一些執行棧,執行上下文堆棧,函數調用棧這樣的詞,其實不必太過咬文嚼字。簡單理解就是每當一個函數被調用的時候,都會爲這個函數建立一個新的上下文。而在一個javascript程序中,一定會有多個執行上下文。javascript以棧(先進後出,後進先出)的方式來處理它們。而調用棧就像一個高速攝影機,會把當前運行的代碼的每一幀都給記錄下來。)
推薦閱讀:js基礎梳理-究竟什麼是執行上下文棧(執行棧),執行上下文(可執行代碼)?
而平常開發中真實的JS運行環境可能包含更多的內容,好比DOM操做(onload, onclick...), Ajax, setTimeout等等。這些是宿主環境(瀏覽器)提供的Web API。而WebAPI自己是不能把執行代碼放到調用棧中執行的,每一個Web Api在執行完成之後會把回調放到事件隊列中。而Event Loop(事件輪詢機制)就是檢查執行棧和任務隊列,若是執行棧已經爲空了,就會將事件隊列中的第一個回調函數放到棧中執行。
promise, Generator, async
看一段代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script> setTimeout(function() { console.log(1); }) new Promise(function(resolve) { console.log(2); for(var i = 0; i < 1000; i++) { i == 99 && resolve(); } console.log(3); }).then(function() { console.log(4); }) console.log(5); </script> </body> </html>
打印順序是 2 3 5 4 1。這裏的promise它的執行順序又是怎麼定的
這裏扯出來另外一個概念,宏任務和微任務。前面事件輪詢機制(Event-Loop)中說到任務隊列,一些Web Api 產生的回調函數在條件達到的時候會被加到任務隊列中。而任務隊列又分爲宏任務(macro-task)和微任務(micro-task)。最新的標準中,它們分別被稱爲task 和jobs。
常見的macro-task大概包括:script(總體代碼), setTimeout,setInterval,setImmediate, I/O, UI rendering
常見的micro-task大概包括:process.nextTick, Promise, MutationObserver(html5新特性)
setTimeout/promise這些咱們都稱之爲任務源,而進入任務隊列的是它們指定的具體執行任務。好比setTimeout的第一個參數回調函數纔是進入任務隊列的任務。
不一樣任務源的任務會進入到不一樣的任務隊列,其中,setTimeout和setInterval是同源的
事件循環的順序,決定了Javascript代碼的執行順序。它從script(總體代碼)開始第一次循環。而後全局上下文進入函數調用棧。直到調用棧清空(只剩全局),而後執行全部的微任務。當全部可執行的微任務執行完畢以後。循環再去從宏任務去找看看還有沒有其它的宏任務隊列,若是有的話就開始第二輪。
上面這段代碼的執行順序就是: