轉載請註明出處:葡萄城官網,葡萄城爲開發者提供專業的開發工具、解決方案和服務,賦能開發者。javascript
原文轉載自 https://neoteric.eu/blog/when-async-is-not-enough-introduction-to-multithreading-in-the-browser/java
先說最重要的:JavaScript代碼能夠異步執行,但並不意味着它是跑在多個線程裏。那麼異步究竟是什麼意思?讓咱們想象發一個Ajax請求,向服務端請求數據。你並非當即獲得響應——你須要等待一小段時間,讓服務端返回數據。在等待響應的過程當中,程序運行着你其餘部分的代碼。若是不是這樣,Ajax請求會凍結住,不讓後面的代碼執行,直到收到服務端的響應——這不是咱們想要的,對吧?webpack
事件循環(Event Loop)web
在JavaScript運行環境中,有個很是重要的概念,叫事件循環。它周而復始地工做着,每一次循環被稱爲一個"tick"。若是在某一個tick中,有等待着的事件隊列須要處理,那麼它們會一個個地被執行。你們所熟知的setTimeout函數就是一個很好的例子。它的第一個參數是一個回調函數——一個在某段時間以後被執行的函數。當setTimeout被解析時,它被壓入函數調用棧的棧頂,它設置一個定時器,而後就從棧頂彈出,把你的回調函數塞到事件循環的後面——那意味着這個回調函數不會精確地在定義的時間間隔後執行——在事件隊列中等待的其餘事件須要被優先處理。當時機到來,你的回調函數被壓入函數調用棧的棧頂,而後執行。你發向服務器的請求,也是一樣的原理——你定義一個回調函數,當收到響應後,它被塞進事件循環隊列的後面。npm
函數調用棧(Call Stack)瀏覽器
函數調用棧是一個底層的數據結構——它記錄咱們運行到程序哪兒了。當程序進入一個函數,就把它放在棧頂,當從函數中返回,就意味着把它從棧中彈出。讓咱們使用一點遞歸式的邏輯來簡單展現一下:服務器
function factorial(n) { if(n === 1 || n === 0) { return 1; } return factorial(n - 1) * n; } console.log(factorial(3)); // 3! = 6
WebWorkers數據結構
你已經看到,異步代碼,解決的是一件事情"如今發生"仍是"之後發生",而不是解決如何讓"多個事情同時發生"。但若是有一些處理器密集型任務,咱們擔憂它會讓界面卡住,怎麼辦?異步
答案是WebWorkers。它容許JavaScript代碼在後臺以一個獨立的線程被執行。它容許主線程流暢運行,不被阻塞。WebWorkers在另外一個與window不一樣的全局上下文環境中。這也帶來了一些侷限:好比,你不能直接在Worker裏操做DOM。最基礎的(也是瀏覽器支持得最好的)WebWorker類型是Dedicated Worker。async
想建立一個Worker,你須要向Worker構造函數傳入一個文件名,在該文件中包含了須要執行的JavaScript腳本。
// 在主線程 var factorialWorker = new Worker('factorial.worker.js');
好比說,咱們想獲得一整組數字的階乘。
想向Worker傳數據,你須要調用postMessage方法:
// 在主線程 var arr = [50, 100, 125, 150]; for(var i = 0; i < arr.length; ++i) { factorialWorker.postMessage(arr[i]); }
你能夠經過事件在主線程和Worker線程之間通訊。若是你想監聽Worker的返回值,就在主線程註冊一個事件監聽器。
// 在主線程 factorialWorker.addEventListener('message', function(event) { console.log('!' + event.data.number + ' = ' + event.data.factorial); });
這會輸出傳入給Worker的數字的階乘。
剩下惟一要作的事情就是建立factorial.workder.js文件。
它須要返回當前計算的數字的階乘,還要定義計算階乘的函數自己。
在Worker中,有一個self屬性。它返回指向WorkerGlobalScope的引用。利用它,咱們能夠和向Worker發送數據的腳本通訊。
// factorial.workder.js function factorial(n) { if(n === 1 || n === 0) { return 1; } return factorial(n - 1) * n; } self.addEventListener('message', function(event) { self.postMessage({ number: event.data, factorial: factorial(event.data) }); });
這裏發生的狀況是,咱們建立了一個新的Worker,並監聽它給咱們返回的數據。而後,咱們向它發送數據——Worker會獲得數據,在完成它內部的計算以後,向咱們發送一個響應。全部的計算都在一個單獨線程中完成。很酷吧?
不過你可能會遇到一些問題。第一個問題是Chrome不能以本地文件的方式使用WebWorkers。不過你能夠開啓一個http服務器來嘗試使用它。
Webpack
另外一個問題可能在你使用Webpack時出現。它可能會給你一個404 Not Found錯誤,由於它不知道你想以WebWorker的形式加載文件。你須要額外的加載器(loader)來加載相似的文件。讓我帶你看看這個過程。首先,用npm安裝加載器:
npm install --save-dev worker-loader
而後你須要在webpack.config.js中添加一條規則:
module: { rules: [ { test: /\.worker\.js$/, use: { loader: 'worker-loader' } }, (...) ] }
如今,若是你引入以.workder.js結尾的文件,Webpack會使用worker-loader來加載。讓咱們用ES6的一些特性來修改一下代碼:
import FactorialWorker from './factorial.worker.js'; const factorialWorker = new FactorialWorker(); factorialWorker.addEventListener('message', event => { console.log(`!${event.data.number} = ${event.data.factorial}`); }); const arrayOfNumbers = [50, 100, 125, 150]; for(let number of arrayOfNumbers) { factorialWorker.postMessage(number); }
總結一下,當開發一個背後有不少操做(尤爲是密集型計算)的富應用時,WebWorkers會很是有幫助。嘗試一下,親自看看吧。我鼓勵你去試驗。