當異步再也不能知足需求:對瀏覽器中的多線程的介紹

轉載請註明出處:葡萄城官網,葡萄城爲開發者提供專業的開發工具、解決方案和服務,賦能開發者。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會很是有幫助。嘗試一下,親自看看吧。我鼓勵你去試驗。  

相關文章
相關標籤/搜索