前言
前幾天,我和一位知乎網友討論這個問題的時候,覺得這非常有意思,所以寫了這篇文章作為記錄
本文的思路和項目代碼來源於知友 @simon3000,我加以修飾以更符合理解的需求。
本文所用代碼已經得到當事人授權,請看:

非常感謝他的理解和鼓勵
作者初始代碼地址
(進入項目頁面,里面的original-version目錄下就是作者的最初的代碼)

通過JS文件和路徑創建webworker帶來的問題
Webworker,我其實一直覺得用法比較生硬,因為似乎需要創建額外的JS文件才能運行,就像下面這樣
var worker =new Worker('work.js’)
這意味着,你需要額外創建一個js文件。這種方式讓我覺得有些“古板”。因為JS操縱文件的能力很差,如果想要創建文件,當然方法也有,參考:https://github.com/eligrey/FileSaver.js/
但是問題在於,如果想要創建文件,JS的文件創建往往離不開下載!我原本只是想“悄無聲息”地創建一個文件,但結果JS在創建的時候突然彈出一個下載框,這可讓人受不了。啊,難受。(此處應有[我太難了]表情包)。
也就是,這時候的webWorker是“靜態”的,是需要額外JS文件的,是受約束的。
四次轉換,將一個普通函數強行變成WebWorker
但是 @simon3000 的建議讓我眼前一亮!他告訴我,根據他使用webworker-loader(webpack技術棧)的經驗,有一種連續轉換的方式可以直接將一個普通函數變成WebWorker

這真是一個令人興奮的信息。
試看看他的操作:
// 文件名為main.js function work () { onmessage = ({data: {message}}) => { console.log ('i am worker, receive:' + message); postMessage ({result: 'message from worker'}); }; } const runWorker = f => { const worker = new Worker ( URL.createObjectURL (new Blob ([`(${f.toString ()})()`])) ); worker.onmessage = ({data: {result}}) => { console.log ('i am main thread, receive:' + result); }; worker.postMessage ({message: 'message from main thread'}); }; const testWorker = runWorker (work);
這段代碼是我在他的代碼基礎上簡化的
輸出結果:

用Promise和閉包的方式去改造
我們再讓它更通用一些,用Promise和閉包的方式去改造它,把runworker函數改造成一個makeworker函數
// 文件名為index.js function work () { onmessage = ({data: {jobId, message}}) => { console.log ('i am worker, receive:-----' + message); postMessage ({jobId, result: 'message from worker'}); }; } const makeWorker = f => { let pendingJobs = {}; const worker = new Worker ( URL.createObjectURL (new Blob ([`(${f.toString ()})()`])) ); worker.onmessage = ({data: {result, jobId}}) => { // 調用resolve,改變Promise狀態 pendingJobs[jobId] (result); // 刪掉,防止key沖突 delete pendingJobs[jobId]; }; return (...message) => new Promise (resolve => { const jobId = String (Math.random ()); pendingJobs[jobId] = resolve; worker.postMessage ({jobId, message}); }); }; const testWorker = makeWorker (work); testWorker ('message from main thread').then (message => { console.log ('i am main thread, i receive:-----' + message); });
輸出結果

總結
這次探討告訴我們什么道理呢?
-
第一,function.toString得到的並不是一個沒有意義的字符串,它是完全可以被用來運行的
-
第二,通過這種方式,webworker不需要借助額外的JS文件了,webworker完全動態化和自由化,你可以在主線程中創建任意個webworker!
-
第三,我通過這次的交談了解到一個道理,編程除了考量邏輯思維,信息差也是考量的一大因素。我之前也想過用webworker做這些事情,可是我不知道能用這樣的四重轉換呀!我也不知道function.toString得到的字符串居然是有作用的。信息差,也是會造成差距的。所以工程上也經驗和前瞻也同樣重要。
其他參考資料
-
worker-loader源碼: https://github.com/webpack-contrib/worker-loader