如何使用 Web Worker 为 JS 创造多线程环境?

Web Worker是什么

我们都知道JS是单线程的,所有任务在一个线程上,一次只能做一件事。虽然可以通过AJAX、定时器等可以实现"并行",但还是没有改变JS单线程的本质,把一些复杂的运算放在页面上执行,还是会导致很卡,甚至卡死

而HTML5标准中的Web Worker为JS创造多线程环境,允许主线程创建Worker线程并给它分配任务,而且在主线程执行任务的时候,worker线程可以同时在后台执行它的任务,互不干扰

这让我们可以将一些复杂运算、高频输入的响应处理、大文件分片上传等放在worker线程处理,最后再返回给主线程。很大程度上缓解了主线程UI渲染阻塞的问题,页面就会很流畅

使用 Web Worker 有几个注意点:

  • 同源限制:worker线程运行的文件必须和主线程的脚本是同源
  • 文件限制:worker线程不能打开本机的文件系统(file://),只能加载网络文件
  • 其他限制:worker线程不能操作DOM、不能使用document、window、parent对象和alert、confirm方法

但可以使用location、navigator,不过只能只读不能改写;还可以使用XMLHttpRequest发送AJAX请求;还可以使用缓存

怎么使用 Web Worker呢?

分别看看在主线程的页面和 worker 线程中的方法和用法

主线程中的方法和使用

创建 worker 进程,直接 new 就完事儿了,而且主线程内和 worker 线程内都可以创建多个 worker 线程

可以传两个参数,第一个是网络脚本文件的链接,不能是本地路径,第二个是配置对象,不是必填

假设引入了一个网络文件 worker1.js

// 主线程
const worker = new Worker('http://worker1.js')

然后使用 worker.postMessage() 方法向 worker 线程发送数据,参数可以是各种数据类型,包括二进制。注意!发送过去的数据是传值,也就是拷贝关系而不是传址,所以 worker 线程内部无论怎么修改,都不会影响到主线程的数据

worker.postMessage('这是发给worker线程的消息')

然后再通过 worker.onmessage 监听并接收 worker 线程处理完返回的数据

worker.onmessage = function(event){
    const data = event.data // 这是返回的数据
}

如果 worker 线程中内部发生错误,会触发主线程的 onerror 事件

worker.onerror( event => {
    console.log( 'worker 线程内部执行报错了', event )
})
// 或者
worker.onerrer = function( event ){
    console.log( 'worker 线程内部执行报错了', event )
}
// 或者
worker.addEventListener('error', event => {
    console.log( 'worker 线程内部执行报错了', event )
})

最后是关掉 worker 线程,因为 worker 线程创建成功就会始终运行,不会主动关闭,所以我们不用的时候要主动关掉,避免浪费资源

worker.terminate() // 这样 worker 线程就关闭了

worker 线程中的方法和使用

worker 线程内部执行上下文跟主线程的执行上下文 window 不一样,是一个叫 WorkerGlobalScope 的东西,我们可以用它或者 self 来访问全局对象

如果主线程创建 worker 线程的时候有传第二个参数,在 worker1.js 里面,也就是在 worker 线程里可以直接这样获取,比如区分多个 worker 线程的时候

// 主线程
const worker = new Worker('http://worker1.js', { name: 'worker1' })

// worker1.js
self.name // worker1  

worker 线程里面,需要监听并接收主线程发送过来的数据。

self.addEventListener('message', event => {
    const data = event.data // 接收到的数据 
    ...
    self.postMessage( '这是处理好的数据' ) // 将处理好的数据发送给主线程
}, false)
// 或者 不要 self 也可以
addEventListener('message', event => {
    const data = event.data // 接收到的数据 
    ...
    self.postMessage( '这是处理好的数据' ) // 将处理好的数据发送给主线程
}, false)

如果 worker 线程内部需要加载其他脚本,只能用importScripts()方法,路径还是只能网络文件,不能使用本地的

importScripts('http://worker2.js', 'http://worker3.js') // 可以引入多个

worker 线程内部也可以使用 onerror 监听错误,和主线程使用方式一样。

worker 线程内部也可以关闭 worker 线程,方法和主线程不一样

self.close() // 这样就从内部关闭了

worker 线程能不能直接写在主线程的页面里

能!

既然它要接收一个网络地址URL,就给它创建一个URL

首先给 script 标签起一个浏览器不认识的type,然后将这个标签里的内容转成二进制对象,然后为这个对象生成URL,再用这个URL创建 worker 线程,如下

<!DOCTYPE html>
    <body>
        <div>这是页面</div>
        <script id="worker" type="xxxxx">
            // worker 线程
            addEventListener('message', event => {
                console.log(event.data) // 收到了吗
                postMessage('收到了') // 发送给主线程
            })
        </script>
        <script type="text/javascript">
            // 主线程
            const blob = new Blob([ document.querySelector('#worker').textContent ])
            const url = window.URL.createObjectURL(blob)
            const worker = new Worker(url)
            worker.postMessage('收到了吗')
            worker.onmessage = event => {
                console.log(event.data) // 收到了
            }
        </script>
    </body>
</html>

worker 线程轮询

function createWorker(fn){
    const blob = new Blob([fn.toString()])
    const url = window.URL.createObjectURL(blob)
    const worker = new Worker(url)
    return worker
}

const webWorker = createWorker(function(){
    let cache;
    
    function compare(new, old){ ... }
    
    setInterval(()=>{
        fetch('/api/xxx').then(res=>{
            let data = res.data
            if(!compare(data, cache)){
                cache = data
                self.postMessage(data)
            }
        })
    },1000)
})

结语

点赞支持、手留余香、与有荣焉

参考

# Web Worker 使用教程

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348

推荐阅读更多精彩内容