前端 Web Workers 到底是什么

作者简介:
钱昱
多年前端工作经验 《JavaScript 设计模式精讲》 作者,主要分享前端方面技术博客
csdn:https://me.csdn.net/qianyu6200430
公众号:前端下午茶

以前我们总说,JS是单线程没有多线程,当JS在页面中运行长耗时同步任务的时候就会导致页面假死影响用户体验,从而需要设置把任务放在任务队列中;执行任务队列中的任务也并非多线程进行的,然而现在HTML5提供了我们前端开发这样的能力 - Web Workers API,我们一起来看一看 Web Worker 是什么,怎么去使用它,在实际生产中如何去用它来进行产出。

  1. 概述
    Web Workers 使得一个Web应用程序可以在与主执行线程分离的后台线程中运行一个脚本操作。这样做的好处是可以在一个单独的线程中执行费时的处理任务,从而允许主(通常是UI)线程运行而不被阻塞。

它的作用就是给JS创造多线程运行环境,允许主线程创建worker线程,分配任务给后者,主线程运行的同时worker线程也在运行,相互不干扰,在worker线程运行结束后把结果返回给主线程。这样做的好处是主线程可以把计算密集型或高延迟的任务交给worker线程执行,这样主线程就会变得轻松,不会被阻塞或拖慢。这并不意味着JS语言本身支持了多线程能力,而是浏览器作为宿主环境提供了JS一个多线程运行的环境。

不过因为worker一旦新建,就会一直运行,不会被主线程的活动打断,这样有利于随时响应主线程的通性,但是也会造成资源的浪费,所以不应过度使用,用完注意关闭。或者说:如果worker无实例引用,该worker空闲后立即会被关闭;如果worker实列引用不为0,该worker空闲也不会被关闭。

看一看它的兼容性

Browser IE Edge FireFox Chrome Safari
version 10+ 12+ 3.5+ 4+ 4+

  1. 使用
    2.1 限制
    worker线程的使用有一些注意点

同源限制 worker线程执行的脚本文件必须和主线程的脚本文件同源,这是当然的了,总不能允许worker线程到别人电脑上到处读文件吧

文件限制 为了安全,worker线程无法读取本地文件,它所加载的脚本必须来自网络,且需要与主线程的脚本同源

DOM操作限制 worker线程在与主线程的window不同的另一个全局上下文中运行,其中无法读取主线程所在网页的DOM对象,也不能获取 document、 window等对象,但是可以获取 navigator、 location(只读)、XMLHttpRequest、 setTimeout族等浏览器API。

通信限制 worker线程与主线程不在同一个上下文,不能直接通信,需要通过 postMessage方法来通信。

脚本限制 worker线程不能执行 alert、 confirm,但可以使用 XMLHttpRequest 对象发出ajax请求。

2.2 例子
在主线程中生成 Worker 线程很容易:

var myWorker = new Worker(jsUrl,options)
Worker()构造函数,第一个参数是脚本的网址(必须遵守同源政策),该参数是必需的,且只能加载 JS 脚本,否则报错。第二个参数是配置对象,该对象可选。它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程。

// 主线程

var
myWorker
=

new

Worker
(
'worker.js'
,

{
name
:

'myWorker'

});

// Worker 线程

self
.
name
// myWorker

关于api什么的,直接上例子大概就能明白了,首先是worker线程的js文件:

// workerThread1.js

let i

1

function
simpleCount
()

{

i
++

self
.
postMessage
(
i
)

setTimeout
(
simpleCount
,

1000
)

}

simpleCount
()

self
.
onmessage
=
ev
=>

{

postMessage
(
ev
.
data

' 呵呵~'
)

}

在HTML文件中的body中:

<div>

Worker 输出内容:
<span

id

'app'

</span>

<input

type

'text'

title

''

id

'msg'

<button

onclick

'
sendMessage
()
'

发送
</button>

<button

onclick

'
stopWorker
()
'

stop!
</button>

</div>

<script

type

'text/javascript'

if

(
typeof
(
Worker
)

===

'undefined'
)

// 使用Worker前检查一下浏览器是否支持

document

.
writeln
(
' Sorry! No Web Worker support.. '
)

else

{

window

.
w
=

new

Worker
(
'workerThread1.js'
)

window

.
w
.
onmessage
=
ev
=>

{

  document

.
getElementById
(
'app'
).
innerHTML
=
ev
.
data

}

window

.
w
.
onerror
=
err
=>

{

  w

.
terminate
()

  console

.
log
(
error
.
filename
,
error
.
lineno
,
error
.
message
)

// 发生错误的文件名、行号、错误内容

}

function
sendMessage
()

{

const
msg
=
document
.
getElementById
(
'msg'
)

  window

.
w
.
postMessage
(
msg
.
value
)

}

function
stopWorker
()

{

  window

.
w
.
terminate
()

}

}

</script>

可以自己运行一下看看效果,上面用到了一些常用的api

主线程中的api, worker表示是 Worker 的实例:

worker.postMessage: 主线程往worker线程发消息,消息可以是任意类型数据,包括二进制数据

worker.terminate: 主线程关闭worker线程

worker.onmessage: 指定worker线程发消息时的回调,也可以通过worker.addEventListener('message',cb)的方式

worker.onerror: 指定worker线程发生错误时的回调,也可以worker.addEventListener('error',cb)

Worker线程中全局对象为 self,代表子线程自身,这时 this指向 self,其上有一些api:

self.postMessage: worker线程往主线程发消息,消息可以是任意类型数据,包括二进制数据

self.close: worker线程关闭自己

self.onmessage: 指定主线程发worker线程消息时的回调,也可以self.addEventListener('message',cb)

self.onerror: 指定worker线程发生错误时的回调,也可以 self.addEventListener('error',cb)

注意, w.postMessage(aMessage,transferList) 方法接受两个参数, aMessage 是可以传递任何类型数据的,包括对象,这种通信是拷贝关系,即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给 Worker,后者再将它还原。一个可选的 Transferable对象的数组,用于传递所有权。如果一个对象的所有权被转移,在发送它的上下文中将变为不可用(中止),并且只有在它被发送到的worker中可用。可转移对象是如ArrayBuffer,MessagePort或ImageBitmap的实例对象, transferList数组中不可传入null。

更详细的API参见 MDN - WorkerGlobalScope。

worker线程中加载脚本的api:

importScripts
(
'script1.js'
)

// 加载单个脚本

importScripts
(
'script1.js'
,

'script2.js'
)

// 加载多个脚本

  1. 实战场景
    个人觉得,Web Worker我们可以当做计算器来用,需要用的时候掏出来摁一摁,不用的时候一定要收起来~

加密数据

有些加解密的算法比较复杂,或者在加解密很多数据的时候,这会非常耗费计算资源,导致UI线程无响应,因此这是使用Web Worker的好时机,使用Worker线程可以让用户更加无缝的操作UI。

预取数据

有时候为了提升数据加载速度,可以提前使用Worker线程获取数据,因为Worker线程是可以是用 XMLHttpRequest 的。

预渲染

在某些渲染场景下,比如渲染复杂的canvas的时候需要计算的效果比如反射、折射、光影、材料等,这些计算的逻辑可以使用Worker线程来执行,也可以使用多个Worker线程,这里有个射线追踪的示例。

复杂数据处理场景

某些检索、排序、过滤、分析会非常耗费时间,这时可以使用Web Worker来进行,不占用主线程。

预加载图片

有时候一个页面有很多图片,或者有几个很大的图片的时候,如果业务限制不考虑懒加载,也可以使用Web Worker来加载图片,可以参考一下这篇文章的探索,这里简单提要一下。

// 主线程

let
w
=

new

Worker
(
"js/workers.js"
);

w
.
onmessage
=

function

(
event
)

{

var
img
=
document
.
createElement
(
"img"
);

img
.
src
=
window
.
URL
.
createObjectURL
(
event
.
data
);

document
.
querySelector
(
'#result'
).
appendChild
(
img
)

}

// worker线程

let
arr
=

[...好多图片路径];

for

(
let
i
=

0
,
len
=
arr
.
length
;
i
<
len
;
i
++)

{

let
req
=

new

XMLHttpRequest
();

req
.
open
(
'GET'
,
arr
[
i
],

true
);

req
.
responseType
=

"blob"
;

req
.
setRequestHeader
(
"client_type"
,

"DESKTOP_WEB"
);

req
.
onreadystatechange
=

()

=>

{

if

(
req
.
readyState
==

4
)

{

 postMessage

(
req
.
response
);

}

}

req
.
send
(
null
);

}

在 Web Workers 实战的时候注意:

虽然使用worker线程不会占用主线程,但是启动worker会比较耗费资源

主线程中使用XMLHttpRequest在请求过程中浏览器另开了一个异步http请求线程,但是交互过程中还是要消耗主线程资源

在 Webpack 项目里面使用 Web Worker 请参照:怎么在 ES6+Webpack 下使用 Web Worker

至于还有Shared Worker、Service Worker 什么的,我们就不看了,IE不喜欢

作者简介:
钱昱
多年前端工作经验 《JavaScript 设计模式精讲》 作者,主要分享前端方面技术博客
csdn:https://me.csdn.net/qianyu6200430
公众号:前端下午茶

本文已经获得钱昱老师授权转发,其他人若有兴趣转载,请直接联系作者授权。

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

推荐阅读更多精彩内容

  • 介绍web worker HTML5提供得,运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能...
    带刀打天下阅读 1,309评论 0 3
  • 一、概述 JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。...
    零星小雨_c84a阅读 2,459评论 0 2
  • 作者:阮一峰www.ruanyifeng.com/blog/2018/07/web-worker.html 概述 ...
    grain先森阅读 1,079评论 0 1
  • 以前我们总说,JS是单线程没有多线程,当JS在页面中运行长耗时同步任务的时候就会导致页面假死影响用户体验,从而需要...
    唐子兮阅读 726评论 0 0
  • 最近在换仓位,把一半的区块链资产换成了Xin,EOS卖了、BTC卖了、ETH卖了、BIG卖了,全部换成了XIN。 ...
    岱挚阅读 508评论 0 2