web workers

全部相关demo

web-workers

运行在后台的 JavaScript,为JavaScript创造多线程环境,独立于其他脚本,不会影响页面。

Worker 线程新建成功,就会始终运行,比较耗费资源,使用完毕后及时关闭。

注意事项

  • Worker 线程运行的脚本文件,必须与主线程的脚本文件同源

  • Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象

  • Worker 线程无法读取本地文件,必须来自网络(所以 demo 也必须启动服务)

使用

  • 主线程创建workers
1
var worker = new Worker('calc.js');
  • 给子线程发送数据,worker.postMessage()
1
worker.postMessage({ name: 'workers', use: 'calc', count: 50 });
  • 子线程接受数据, 直接使用表达 onmessage 即可,或函数声明 onmessage。
1
2
3
4
5
6
7
8
9
10
onmessage = function(e) {
const data = e.data;
console.log(data);
}

// 等同于
this.onmessage = function(e) {
const data = e.data;
console.log(data);
}
  • 处理成功后数据通过 postMessage(result); 回传递到主进程, 主进程同样通过 onmessage 监听
1
2
3
4
worker.onmessage = function (e) {
const result = e.data;
console.log(result);
}
  • 数据处理完后,终止进程, 可以在主进程终止,也可以在子进程开始, worker 线程会被立即杀死
1
2
3
4
5
// 主进程终止方式
worker.terminate();

// 子进程终止方式
close();

完成通信代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// b.html 主进程
<html>
<head>
</head>
<body>
<div>web-workers</div>
<script>
var worker = new Worker('./calc.js');
window.onload = function () {
// 给子进程传递数据
worker.postMessage({
name: 'workers',
use: 'calc',
count: 8
});
};
// 获取子进程处理的结果
worker.onmessage = function (e) {
const result = e.data;
console.log(result);
}

// worker.terminate();
</script>
</body>
</html>

// 子进程 calc.js
onmessage = function(e) {
const data = e.data;
const count = data.count;
const result = fibonacci(count);
// 子进程终止
// close();
console.log(result);
// 计算完成的结果回传到主进程
postMessage(result);
}

// 密集型计算
function fibonacci (n) {
if (n==0 || n == 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}

多个子进程

需要的话 worker 能够生成更多的 worker(subworker)

注意事项

  • 它们必须托管在同源的父页面内

  • subworker 解析 URI 时会相对于父 worker 的地址而不是自身页面的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 主进程所在js文件声明
var worker = new Worker('./calc.js');
var worker2 = new Worker('./calc2.js');

// 给多个子进程传递值
worker.postMessage({
name: 'workers',
use: 'calc',
count: 8
});
worker2.postMessage({
name: 'workers',
use: 'calc',
count: 8
});

Worker 加载脚本

如果计算非常复杂,可能需要模块化拆分,worker 提供了加载脚本功能, 导入后可直接调用

1
2
3
4
5
6
7
8
// main.js
importScripts('sub.js', 'sub2.js');
getAll() // 3

// sub.js
function getAll() {
return 3
}

嵌入式 worker

  • Worker 可以载入单独的 JavaScript 脚本文件,也可以载入script标签形式

  • 一个 script 元素没有 src 特性,并且它的 type 特性没有指定成一个可运行的 mime-type,那么它就会被认为是一个数据块元素,并且能够被 JavaScript 使用, 你能够以如下方式嵌入一个 worker

  • 所以使用时 script 不应该具有src以及type应该是无法识别的类型

  • 将script脚本内容生成 URL,再让 Worker 加载这个 URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// index.html

// worker 子进程
<script id="worker" type="app/worker">
// 该脚本不会被 JS 引擎解析,因为它的 mime-type 是 text/js-worker。
addEventListener('message', function () {
postMessage('some message');
}, false);
// 剩下的 worker 代码写到这里。
</script>

// 主进程
var blob = new Blob([document.getElementById('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);

worker.onmessage = function (e) {
console.log(e, '同页面的worker')
};

共享workder

  • 一个共享worker可以被多个脚本使用——即使这些脚本正在被不同的window、iframe或者worker访问

  • 共享worker通信必须通过端口对象,供脚本与worker通信

  • 在传递消息之前,端口连接必须被显式的打开,打开方式是使用onmessage事件处理函数或者start()方法

  • start()方法的调用只在一种情况下需要,那就是消息事件被addEventListener()方法使用

  • 在使用start()方法打开端口连接时,如果父级线程和worker线程需要双向通信,那么它们都需要调用start()方法

  • 如果共享worker可以被多个浏览上下文调用,所有这些浏览上下文必须属于同源(相同的协议,主机和端口号)

  • 当一个端口连接被创建时(例如:在父级线程中,设置onmessage事件处理函数,或者显式调用start()方法时),使用onconnect事件处理函数来执行代码

代码实现

  • 创建端口连接,使用onconnect事件处理函数来执行代码

  • 使用事件的ports属性来获取端口并存储在变量中

  • 然后,为端口添加一个消息处理函数用来做运算并回传结果给主线程

  • 最后,回到主脚本,我们处理消息

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<html>
<head>
</head>
<body>
<div>web-workers</div>
<input id="number1"/>
<input id="result"/>
<a href="./index2.html">去页面2</a>
<script>
var first = document.querySelector('#number1');
var result1 = document.querySelector('#result');

if (!!window.SharedWorker) {
// 构造器的名字不同
var myWorker = new SharedWorker("./common.js");

first.onchange = function() {
myWorker.port.postMessage([first.value, first.value]);
}

myWorker.port.onmessage = function(e) {
result1.value = e.data;
}
}
</script>
</body>
</html>

page2.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<html>
<head>
</head>
<body>
<div>web-workers</div>
<input id="number3"/>
<input id="result"/>
<script>
</script>
<script>
var justNum = document.querySelector('#number3');
var result2 = document.querySelector('#result');

if (!!window.SharedWorker) {
// 构造器的名字不同
var myWorker = new SharedWorker("./common.js");

justNum.onchange = function() {
myWorker.port.postMessage([justNum.value, justNum.value]);
}

myWorker.port.onmessage = function(e) {
result.value = e.data;
}
}

</script>
</body>
</html>

common.js 端口对象

1
2
3
4
5
6
7
8
// 使用事件的ports属性来获取端口并存储在变量中
onconnect = function(e) {
var port = e.ports[0];
port.onmessage = function(e) {
var workerResult = (e.data[0] * e.data[1]);
port.postMessage(workerResult);
}
}

转让所有权来传递数据

  • 转让对象从一个上下文转移到另一个上下文而不会经过任何拷贝操作,当传递大数据(或者媒体数据)时会获得极大的性能提升

  • 并不是所有的数据都可以转让的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<head>
</head>
<body>
<div>web-workers</div>
<script>
var worker = new Worker('./tran.js')
// 必须是可转让对象
var uInt8Array = new Uint8Array(1024*1024*200); // 200MB
for (var i = 0; i < uInt8Array .length; ++i) {
uInt8Array[i] = i;
}
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
console.log(uInt8Array.buffer, 'main')
</script>
</body>
</html>

// tran.js
this.onmessage = function (e) {
console.log(e.data)
}

workers可用函数和接口

  • Navigator

  • XMLHttpRequest

  • Array, Date, Math, String

  • WindowTimers.setTimeout, WindowTimers.setInterval

返回
顶部