Web 开发人员版 — 最后更新于 2024 年 9 月 12 日
所有当前引擎都支持。
本规范定义了一个 API,用于在后台独立于任何用户界面脚本运行脚本。
这允许长时间运行的脚本不受响应点击或其他用户交互的脚本的干扰,并允许执行长时间任务而不会产生延迟以保持页面响应。
工作线程(此处将这些后台脚本称为工作线程)相对重量级,并且不适合大量使用。例如,为四百万像素图像的每个像素启动一个工作线程是不合适的。以下示例显示了一些工作线程的适当用途。
通常,工作线程预计具有较长的生命周期、较高的启动性能成本和较高的每个实例内存成本。
工作线程可以用于各种用途。以下子部分展示了这些用途的各种示例。
工作线程最简单的用途是在不中断用户界面的情况下执行计算量大的任务。
在此示例中,主文档生成一个工作线程来(简单地)计算质数,并逐步显示最近找到的质数。
主页面如下所示
<!DOCTYPE HTML>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > Worker example: One-core computation</ title >
</ head >
< body >
< p > The highest prime number discovered so far is: < output id = "result" ></ output ></ p >
< script >
var worker = new Worker( 'worker.js' );
worker. onmessage = function ( event) {
document. getElementById( 'result' ). textContent = event. data;
};
</ script >
</ body >
</ html >
Worker()
构造函数调用创建了一个工作线程并返回一个表示该工作线程的 Worker
对象,该对象用于与工作线程通信。该对象的 onmessage
事件处理程序允许代码接收来自工作线程的消息。
工作线程本身如下所示
var n = 1 ;
search: while ( true ) {
n += 1 ;
for ( var i = 2 ; i <= Math. sqrt( n); i += 1 )
if ( n % i == 0 )
continue search;
// found a prime!
postMessage( n);
}
此代码的大部分内容只是一个未优化的质数搜索。当找到质数时,postMessage()
方法用于向页面发送消息。
我们到目前为止的所有示例都显示了运行经典脚本的工作线程。工作线程可以使用模块脚本实例化,这具有通常的好处:能够使用 JavaScript import
语句导入其他模块;默认情况下为严格模式;以及顶级声明不会污染工作线程的全局作用域。
由于 import
语句可用,因此importScripts()
方法将在模块工作线程内部自动失败。
在此示例中,主文档使用工作线程执行脱离开发线程的图像处理。它从另一个模块导入使用的过滤器。
主页面如下所示
<!DOCTYPE html>
< html lang = "en" >
< meta charset = "utf-8" >
< title > Worker example: image decoding</ title >
< p >
< label >
Type an image URL to decode
< input type = "url" id = "image-url" list = "image-list" >
< datalist id = "image-list" >
< option value = "https://html.whatwg.com.cn/images/drawImage.png" >
< option value = "https://html.whatwg.com.cn/images/robots.jpeg" >
< option value = "https://html.whatwg.com.cn/images/arcTo2.png" >
</ datalist >
</ label >
</ p >
< p >
< label >
Choose a filter to apply
< select id = "filter" >
< option value = "none" > none</ option >
< option value = "grayscale" > grayscale</ option >
< option value = "brighten" > brighten by 20%</ option >
</ select >
</ label >
</ p >
< div id = "output" ></ div >
< script type = "module" >
const worker = new Worker( "worker.js" , { type: "module" });
worker. onmessage = receiveFromWorker;
const url = document. querySelector( "#image-url" );
const filter = document. querySelector( "#filter" );
const output = document. querySelector( "#output" );
url. oninput = updateImage;
filter. oninput = sendToWorker;
let imageData, context;
function updateImage() {
const img = new Image();
img. src = url. value;
img. onload = () => {
const canvas = document. createElement( "canvas" );
canvas. width = img. width;
canvas. height = img. height;
context = canvas. getContext( "2d" );
context. drawImage( img, 0 , 0 );
imageData = context. getImageData( 0 , 0 , canvas. width, canvas. height);
sendToWorker();
output. replaceChildren( canvas);
};
}
function sendToWorker() {
worker. postMessage({ imageData, filter: filter. value });
}
function receiveFromWorker( e) {
context. putImageData( e. data, 0 , 0 );
}
</ script >
然后工作线程文件为
import * as filters from "./filters.js" ;
self. onmessage = e => {
const { imageData, filter } = e. data;
filters[ filter]( imageData);
self. postMessage( imageData, [ imageData. data. buffer]);
};
它导入文件 filters.js
export function none() {}
export function grayscale({ data: d }) {
for ( let i = 0 ; i < d. length; i += 4 ) {
const [ r, g, b] = [ d[ i], d[ i + 1 ], d[ i + 2 ]];
// CIE luminance for the RGB
// The human eye is bad at seeing red and blue, so we de-emphasize them.
d[ i] = d[ i + 1 ] = d[ i + 2 ] = 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
};
export function brighten({ data: d }) {
for ( let i = 0 ; i < d. length; ++ i) {
d[ i] *= 1.2 ;
}
};
所有当前引擎都支持。
本节使用 Hello World 示例介绍共享工作线程。共享工作线程使用略微不同的 API,因为每个工作线程可以有多个连接。
第一个示例演示了如何连接到工作线程以及工作线程如何在连接到页面时向页面发送消息。接收到的消息显示在日志中。
这是 HTML 页面
<!DOCTYPE HTML>
< html lang = "en" >
< meta charset = "utf-8" >
< title > Shared workers: demo 1</ title >
< pre id = "log" > Log:</ pre >
< script >
var worker = new SharedWorker( 'test.js' );
var log = document. getElementById( 'log' );
worker. port. onmessage = function ( e) { // note: not worker.onmessage!
log. textContent += '\n' + e. data;
}
</ script >
这是 JavaScript 工作线程
onconnect = function ( e) {
var port = e. ports[ 0 ];
port. postMessage( 'Hello World!' );
}
第二个示例通过更改两件事扩展了第一个示例:首先,使用 addEventListener()
而不是 事件处理程序 IDL 属性 接收消息,其次,向工作线程发送消息,导致工作线程发送另一条消息作为回复。接收到的消息再次显示在日志中。
这是 HTML 页面
<!DOCTYPE HTML>
< html lang = "en" >
< meta charset = "utf-8" >
< title > Shared workers: demo 2</ title >
< pre id = "log" > Log:</ pre >
< script >
var worker = new SharedWorker( 'test.js' );
var log = document. getElementById( 'log' );
worker. port. addEventListener( 'message' , function ( e) {
log. textContent += '\n' + e. data;
}, false );
worker. port. start(); // note: need this when using addEventListener
worker. port. postMessage( 'ping' );
</ script >
这是 JavaScript 工作线程
onconnect = function ( e) {
var port = e. ports[ 0 ];
port. postMessage( 'Hello World!' );
port. onmessage = function ( e) {
port. postMessage( 'pong' ); // not e.ports[0].postMessage!
// e.target.postMessage('pong'); would work also
}
}
最后,扩展了示例以演示两个页面如何连接到同一个工作线程;在本例中,第二个页面仅仅位于第一个页面的 iframe
中,但相同的原理也适用于单独的 顶级可遍历对象 中的完全独立的页面。
这是外部 HTML 页面
<!DOCTYPE HTML>
< html lang = "en" >
< meta charset = "utf-8" >
< title > Shared workers: demo 3</ title >
< pre id = "log" > Log:</ pre >
< script >
var worker = new SharedWorker( 'test.js' );
var log = document. getElementById( 'log' );
worker. port. addEventListener( 'message' , function ( e) {
log. textContent += '\n' + e. data;
}, false );
worker. port. start();
worker. port. postMessage( 'ping' );
</ script >
< iframe src = "inner.html" ></ iframe >
这是内部 HTML 页面
<!DOCTYPE HTML>
< html lang = "en" >
< meta charset = "utf-8" >
< title > Shared workers: demo 3 inner frame</ title >
< pre id = log > Inner log:</ pre >
< script >
var worker = new SharedWorker( 'test.js' );
var log = document. getElementById( 'log' );
worker. port. onmessage = function ( e) {
log. textContent += '\n' + e. data;
}
</ script >
这是 JavaScript 工作线程
var count = 0 ;
onconnect = function ( e) {
count += 1 ;
var port = e. ports[ 0 ];
port. postMessage( 'Hello World! You are connection #' + count);
port. onmessage = function ( e) {
port. postMessage( 'pong' );
}
}
在此示例中,可以打开多个窗口(查看器),这些窗口都查看同一张地图。所有窗口共享相同的地图信息,单个工作线程协调所有查看器。每个查看器可以独立移动,但如果它们在地图上设置任何数据,则所有查看器都会更新。
主页面没什么意思,它只是提供了一种打开查看器的方法
<!DOCTYPE HTML>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > Workers example: Multiviewer</ title >
< script >
function openViewer() {
window. open( 'viewer.html' );
}
</ script >
</ head >
< body >
< p >< button type = button onclick = "openViewer()" > Open a new
viewer</ button ></ p >
< p > Each viewer opens in a new window. You can have as many viewers
as you like, they all view the same data.</ p >
</ body >
</ html >
查看器更复杂
<!DOCTYPE HTML>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > Workers example: Multiviewer viewer</ title >
< script >
var worker = new SharedWorker( 'worker.js' , 'core' );
// CONFIGURATION
function configure( event) {
if ( event. data. substr( 0 , 4 ) != 'cfg ' ) return ;
var name = event. data. substr( 4 ). split( ' ' , 1 )[ 0 ];
// update display to mention our name is name
document. getElementsByTagName( 'h1' )[ 0 ]. textContent += ' ' + name;
// no longer need this listener
worker. port. removeEventListener( 'message' , configure, false );
}
worker. port. addEventListener( 'message' , configure, false );
// MAP
function paintMap( event) {
if ( event. data. substr( 0 , 4 ) != 'map ' ) return ;
var data = event. data. substr( 4 ). split( ',' );
// display tiles data[0] .. data[8]
var canvas = document. getElementById( 'map' );
var context = canvas. getContext( '2d' );
for ( var y = 0 ; y < 3 ; y += 1 ) {
for ( var x = 0 ; x < 3 ; x += 1 ) {
var tile = data[ y * 3 + x];
if ( tile == '0' )
context. fillStyle = 'green' ;
else
context. fillStyle = 'maroon' ;
context. fillRect( x * 50 , y * 50 , 50 , 50 );
}
}
}
worker. port. addEventListener( 'message' , paintMap, false );
// PUBLIC CHAT
function updatePublicChat( event) {
if ( event. data. substr( 0 , 4 ) != 'txt ' ) return ;
var name = event. data. substr( 4 ). split( ' ' , 1 )[ 0 ];
var message = event. data. substr( 4 + name. length + 1 );
// display "<name> message" in public chat
var public = document. getElementById( 'public' );
var p = document. createElement( 'p' );
var n = document. createElement( 'button' );
n. textContent = '<' + name + '> ' ;
n. onclick = function () { worker. port. postMessage( 'msg ' + name); };
p. appendChild( n);
var m = document. createElement( 'span' );
m. textContent = message;
p. appendChild( m);
public. appendChild( p);
}
worker. port. addEventListener( 'message' , updatePublicChat, false );
// PRIVATE CHAT
function startPrivateChat( event) {
if ( event. data. substr( 0 , 4 ) != 'msg ' ) return ;
var name = event. data. substr( 4 ). split( ' ' , 1 )[ 0 ];
var port = event. ports[ 0 ];
// display a private chat UI
var ul = document. getElementById( 'private' );
var li = document. createElement( 'li' );
var h3 = document. createElement( 'h3' );
h3. textContent = 'Private chat with ' + name;
li. appendChild( h3);
var div = document. createElement( 'div' );
var addMessage = function ( name, message) {
var p = document. createElement( 'p' );
var n = document. createElement( 'strong' );
n. textContent = '<' + name + '> ' ;
p. appendChild( n);
var t = document. createElement( 'span' );
t. textContent = message;
p. appendChild( t);
div. appendChild( p);
};
port. onmessage = function ( event) {
addMessage( name, event. data);
};
li. appendChild( div);
var form = document. createElement( 'form' );
var p = document. createElement( 'p' );
var input = document. createElement( 'input' );
input. size = 50 ;
p. appendChild( input);
p. appendChild( document. createTextNode( ' ' ));
var button = document. createElement( 'button' );
button. textContent = 'Post' ;
p. appendChild( button);
form. onsubmit = function () {
port. postMessage( input. value);
addMessage( 'me' , input. value);
input. value = '' ;
return false ;
};
form. appendChild( p);
li. appendChild( form);
ul. appendChild( li);
}
worker. port. addEventListener( 'message' , startPrivateChat, false );
worker. port. start();
</ script >
</ head >
< body >
< h1 > Viewer</ h1 >
< h2 > Map</ h2 >
< p >< canvas id = "map" height = 150 width = 150 ></ canvas ></ p >
< p >
< button type = button onclick = "worker.port.postMessage('mov left')" > Left</ button >
< button type = button onclick = "worker.port.postMessage('mov up')" > Up</ button >
< button type = button onclick = "worker.port.postMessage('mov down')" > Down</ button >
< button type = button onclick = "worker.port.postMessage('mov right')" > Right</ button >
< button type = button onclick = "worker.port.postMessage('set 0')" > Set 0</ button >
< button type = button onclick = "worker.port.postMessage('set 1')" > Set 1</ button >
</ p >
< h2 > Public Chat</ h2 >
< div id = "public" ></ div >
< form onsubmit = "worker.port.postMessage('txt ' + message.value); message.value = ''; return false;" >
< p >
< input type = "text" name = "message" size = "50" >
< button > Post</ button >
</ p >
</ form >
< h2 > Private Chat</ h2 >
< ul id = "private" ></ ul >
</ body >
</ html >
查看器编写方式有一些关键要点需要注意。
多个侦听器。代码在此处附加多个事件侦听器,而不是单个消息处理函数,每个侦听器都会快速检查它是否与消息相关。在此示例中,这没有多大区别,但如果多个作者想要使用单个端口与工作线程通信进行协作,它将允许使用独立的代码,而不是必须对单个事件处理函数进行所有更改。
以这种方式注册事件侦听器还允许您在完成使用后注销特定侦听器,如本示例中的 configure()
方法所示。
最后,工作线程
var nextName = 0 ;
function getNextName() {
// this could use more friendly names
// but for now just return a number
return nextName++ ;
}
var map = [
[ 0 , 0 , 0 , 0 , 0 , 0 , 0 ],
[ 1 , 1 , 0 , 1 , 0 , 1 , 1 ],
[ 0 , 1 , 0 , 1 , 0 , 0 , 0 ],
[ 0 , 1 , 0 , 1 , 0 , 1 , 1 ],
[ 0 , 0 , 0 , 1 , 0 , 0 , 0 ],
[ 1 , 0 , 0 , 1 , 1 , 1 , 1 ],
[ 1 , 1 , 0 , 1 , 1 , 0 , 1 ],
];
function wrapX( x) {
if ( x < 0 ) return wrapX( x + map[ 0 ]. length);
if ( x >= map[ 0 ]. length) return wrapX( x - map[ 0 ]. length);
return x;
}
function wrapY( y) {
if ( y < 0 ) return wrapY( y + map. length);
if ( y >= map[ 0 ]. length) return wrapY( y - map. length);
return y;
}
function wrap( val, min, max) {
if ( val < min)
return val + ( max- min) + 1 ;
if ( val > max)
return val - ( max- min) - 1 ;
return val;
}
function sendMapData( viewer) {
var data = '' ;
for ( var y = viewer. y- 1 ; y <= viewer. y+ 1 ; y += 1 ) {
for ( var x = viewer. x- 1 ; x <= viewer. x+ 1 ; x += 1 ) {
if ( data != '' )
data += ',' ;
data += map[ wrap( y, 0 , map[ 0 ]. length- 1 )][ wrap( x, 0 , map. length- 1 )];
}
}
viewer. port. postMessage( 'map ' + data);
}
var viewers = {};
onconnect = function ( event) {
var name = getNextName();
event. ports[ 0 ]. _data = { port: event. ports[ 0 ], name: name, x: 0 , y: 0 , };
viewers[ name] = event. ports[ 0 ]. _data;
event. ports[ 0 ]. postMessage( 'cfg ' + name);
event. ports[ 0 ]. onmessage = getMessage;
sendMapData( event. ports[ 0 ]. _data);
};
function getMessage( event) {
switch ( event. data. substr( 0 , 4 )) {
case 'mov ' :
var direction = event. data. substr( 4 );
var dx = 0 ;
var dy = 0 ;
switch ( direction) {
case 'up' : dy = - 1 ; break ;
case 'down' : dy = 1 ; break ;
case 'left' : dx = - 1 ; break ;
case 'right' : dx = 1 ; break ;
}
event. target. _data. x = wrapX( event. target. _data. x + dx);
event. target. _data. y = wrapY( event. target. _data. y + dy);
sendMapData( event. target. _data);
break ;
case 'set ' :
var value = event. data. substr( 4 );
map[ event. target. _data. y][ event. target. _data. x] = value;
for ( var viewer in viewers)
sendMapData( viewers[ viewer]);
break ;
case 'txt ' :
var name = event. target. _data. name;
var message = event. data. substr( 4 );
for ( var viewer in viewers)
viewers[ viewer]. port. postMessage( 'txt ' + name + ' ' + message);
break ;
case 'msg ' :
var party1 = event. target. _data;
var party2 = viewers[ event. data. substr( 4 ). split( ' ' , 1 )[ 0 ]];
if ( party2) {
var channel = new MessageChannel();
party1. port. postMessage( 'msg ' + party2. name, [ channel. port1]);
party2. port. postMessage( 'msg ' + party1. name, [ channel. port2]);
}
break ;
}
}
连接到多个页面。脚本使用 onconnect
事件侦听器来侦听多个连接。
直接通道。当工作线程从一个查看器接收“msg”消息并命名另一个查看器时,它会在两者之间建立直接连接,以便两个查看器可以直接通信,而无需工作线程代理所有消息。
随着多核 CPU 变得越来越普遍,获得更好性能的一种方法是在多个工作线程之间拆分计算量大的任务。在此示例中,要为从 1 到 10,000,000 的每个数字执行的计算量大的任务被分配给十个子工作线程。
主页面如下所示,它只报告结果
<!DOCTYPE HTML>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > Worker example: Multicore computation</ title >
</ head >
< body >
< p > Result: < output id = "result" ></ output ></ p >
< script >
var worker = new Worker( 'worker.js' );
worker. onmessage = function ( event) {
document. getElementById( 'result' ). textContent = event. data;
};
</ script >
</ body >
</ html >
工作线程本身如下所示
// settings
var num_workers = 10 ;
var items_per_worker = 1000000 ;
// start the workers
var result = 0 ;
var pending_workers = num_workers;
for ( var i = 0 ; i < num_workers; i += 1 ) {
var worker = new Worker( 'core.js' );
worker. postMessage( i * items_per_worker);
worker. postMessage(( i+ 1 ) * items_per_worker);
worker. onmessage = storeResult;
}
// handle the results
function storeResult( event) {
result += 1 * event. data;
pending_workers -= 1 ;
if ( pending_workers <= 0 )
postMessage( result); // finished!
}
它包含一个循环来启动子工作线程,然后是一个处理程序,等待所有子工作线程响应。
子工作线程的实现如下
var start;
onmessage = getStart;
function getStart( event) {
start = 1 * event. data;
onmessage = getEnd;
}
var end;
function getEnd( event) {
end = 1 * event. data;
onmessage = null ;
work();
}
function work() {
var result = 0 ;
for ( var i = start; i < end; i += 1 ) {
// perform some complex calculation here
result += 1 ;
}
postMessage( result);
close();
}
它们在两个事件中接收两个数字,对由此指定范围内的数字执行计算,然后将结果报告回父级。
假设提供了三个任务的加密库
库本身如下所示
function handleMessage( e) {
if ( e. data == "genkeys" )
genkeys( e. ports[ 0 ]);
else if ( e. data == "encrypt" )
encrypt( e. ports[ 0 ]);
else if ( e. data == "decrypt" )
decrypt( e. ports[ 0 ]);
}
function genkeys( p) {
var keys = _generateKeyPair();
p. postMessage( keys[ 0 ]);
p. postMessage( keys[ 1 ]);
}
function encrypt( p) {
var key, state = 0 ;
p. onmessage = function ( e) {
if ( state == 0 ) {
key = e. data;
state = 1 ;
} else {
p. postMessage( _encrypt( key, e. data));
}
};
}
function decrypt( p) {
var key, state = 0 ;
p. onmessage = function ( e) {
if ( state == 0 ) {
key = e. data;
state = 1 ;
} else {
p. postMessage( _decrypt( key, e. data));
}
};
}
// support being used as a shared worker as well as a dedicated worker
if ( 'onmessage' in this ) // dedicated worker
onmessage = handleMessage;
else // shared worker
onconnect = function ( e) { e. port. onmessage = handleMessage; }
// the "crypto" functions:
function _generateKeyPair() {
return [ Math. random(), Math. random()];
}
function _encrypt( k, s) {
return 'encrypted-' + k + ' ' + s;
}
function _decrypt( k, s) {
return s. substr( s. indexOf( ' ' ) + 1 );
}
请注意,此处的加密函数只是存根,不执行真正的加密。
此库可以使用以下方法
<!DOCTYPE HTML>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > Worker example: Crypto library</ title >
< script >
const cryptoLib = new Worker( 'libcrypto-v1.js' ); // or could use 'libcrypto-v2.js'
function startConversation( source, message) {
const messageChannel = new MessageChannel();
source. postMessage( message, [ messageChannel. port2]);
return messageChannel. port1;
}
function getKeys() {
let state = 0 ;
startConversation( cryptoLib, "genkeys" ). onmessage = function ( e) {
if ( state === 0 )
document. getElementById( 'public' ). value = e. data;
else if ( state === 1 )
document. getElementById( 'private' ). value = e. data;
state += 1 ;
};
}
function enc() {
const port = startConversation( cryptoLib, "encrypt" );
port. postMessage( document. getElementById( 'public' ). value);
port. postMessage( document. getElementById( 'input' ). value);
port. onmessage = function ( e) {
document. getElementById( 'input' ). value = e. data;
port. close();
};
}
function dec() {
const port = startConversation( cryptoLib, "decrypt" );
port. postMessage( document. getElementById( 'private' ). value);
port. postMessage( document. getElementById( 'input' ). value);
port. onmessage = function ( e) {
document. getElementById( 'input' ). value = e. data;
port. close();
};
}
</ script >
< style >
textarea { display : block ; }
</ style >
</ head >
< body onload = "getKeys()" >
< fieldset >
< legend > Keys</ legend >
< p >< label > Public Key: < textarea id = "public" ></ textarea ></ label ></ p >
< p >< label > Private Key: < textarea id = "private" ></ textarea ></ label ></ p >
</ fieldset >
< p >< label > Input: < textarea id = "input" ></ textarea ></ label ></ p >
< p >< button onclick = "enc()" > Encrypt</ button > < button onclick = "dec()" > Decrypt</ button ></ p >
</ body >
</ html >
但是,API 的后续版本可能希望将所有加密工作卸载到子工作线程上。这可以通过以下方式完成
function handleMessage( e) {
if ( e. data == "genkeys" )
genkeys( e. ports[ 0 ]);
else if ( e. data == "encrypt" )
encrypt( e. ports[ 0 ]);
else if ( e. data == "decrypt" )
decrypt( e. ports[ 0 ]);
}
function genkeys( p) {
var generator = new Worker( 'libcrypto-v2-generator.js' );
generator. postMessage( '' , [ p]);
}
function encrypt( p) {
p. onmessage = function ( e) {
var key = e. data;
var encryptor = new Worker( 'libcrypto-v2-encryptor.js' );
encryptor. postMessage( key, [ p]);
};
}
function encrypt( p) {
p. onmessage = function ( e) {
var key = e. data;
var decryptor = new Worker( 'libcrypto-v2-decryptor.js' );
decryptor. postMessage( key, [ p]);
};
}
// support being used as a shared worker as well as a dedicated worker
if ( 'onmessage' in this ) // dedicated worker
onmessage = handleMessage;
else // shared worker
onconnect = function ( e) { e. ports[ 0 ]. onmessage = handleMessage };
然后,小型子工作线程将如下所示。
用于生成密钥对
onmessage = function ( e) {
var k = _generateKeyPair();
e. ports[ 0 ]. postMessage( k[ 0 ]);
e. ports[ 0 ]. postMessage( k[ 1 ]);
close();
}
function _generateKeyPair() {
return [ Math. random(), Math. random()];
}
用于加密
onmessage = function ( e) {
var key = e. data;
e. ports[ 0 ]. onmessage = function ( e) {
var s = e. data;
postMessage( _encrypt( key, s));
}
}
function _encrypt( k, s) {
return 'encrypted-' + k + ' ' + s;
}
用于解密
onmessage = function ( e) {
var key = e. data;
e. ports[ 0 ]. onmessage = function ( e) {
var s = e. data;
postMessage( _decrypt( key, s));
}
}
function _decrypt( k, s) {
return s. substr( s. indexOf( ' ' ) + 1 );
}
请注意,API 的用户甚至不必知道发生了这种情况——API 没有改变;库可以在不更改其 API 的情况下委托给子工作线程,即使它正在使用消息通道接受数据。
创建工作线程需要一个指向 JavaScript 文件的 URL。Worker()
构造函数只使用指向该文件的 URL 作为其参数调用;然后创建工作线程并返回
var worker = new Worker( 'helper.js' );
如果您希望您的工作线程脚本被解释为模块脚本而不是默认的经典脚本,则需要使用稍微不同的签名。
var worker = new Worker( 'helper.mjs' , { type: "module" });
专用工作线程在后台使用MessagePort
对象,因此支持所有相同的功能,例如发送结构化数据、传输二进制数据和传输其他端口。
要接收来自专用工作线程的消息,请在Worker
对象上使用onmessage
事件处理程序 IDL 属性。
worker. onmessage = function ( event) { ... };
您还可以使用addEventListener()
方法。
专用工作线程使用的隐式MessagePort
在其创建时会隐式启用其端口消息队列,因此在Worker
接口上没有等效于MessagePort
接口的start()
方法。
要发送数据到工作线程,请使用postMessage()
方法。结构化数据可以通过此通信通道发送。要有效地发送ArrayBuffer
对象(通过传输而不是克隆它们),请将它们列在第二个参数的数组中。
worker. postMessage({
operation: 'find-edges' ,
input: buffer, // an ArrayBuffer object
threshold: 0.6 ,
}, [ buffer]);
要在工作线程内部接收消息,使用onmessage
事件处理程序 IDL 属性。
onmessage = function ( event) { ... };
您还可以再次使用addEventListener()
方法。
在这两种情况下,数据都提供在事件对象的data
属性中。
要发送回复消息,您再次使用postMessage()
。它以相同的方式支持结构化数据。
postMessage( event. data. input, [ event. data. input]); // transfer the buffer back
所有当前引擎都支持。
共享工作线程由用于创建它的脚本的 URL 标识,可以选择使用显式名称。该名称允许启动特定共享工作线程的多个实例。
共享工作线程按来源限定范围。使用相同名称的两个不同站点不会发生冲突。但是,如果一个页面尝试使用与同一站点上的另一个页面相同的共享工作线程名称,但使用不同的脚本 URL,则会失败。
创建共享工作线程是使用SharedWorker()
构造函数完成的。此构造函数将要使用的脚本的 URL 作为其第一个参数,以及工作线程的名称(如果有)作为第二个参数。
var worker = new SharedWorker( 'service.js' );
与共享工作线程的通信是通过显式的MessagePort
对象完成的。由SharedWorker()
构造函数返回的对象在其port
属性中保存对端口的引用。
worker. port. onmessage = function ( event) { ... };
worker. port. postMessage( 'some message' );
worker. port. postMessage({ foo: 'structured' , bar: [ 'data' , 'also' , 'possible' ]});
在共享工作线程内部,使用connect
事件宣布工作线程的新客户端。新客户端的端口由事件对象的source
属性给出。
onconnect = function ( event) {
var newPort = event. source;
// set up a listener
newPort. onmessage = function ( event) { ... };
// send a message back to the port
newPort. postMessage( 'ready!' ); // can also send structured data, of course
};
本标准定义了两种工作线程:专用工作线程和共享工作线程。专用工作线程一旦创建,就会与其创建者链接,但可以使用消息端口从专用工作线程与多个其他浏览上下文或工作线程通信。另一方面,共享工作线程是命名的,一旦创建,在相同来源中运行的任何脚本都可以获取对该工作线程的引用并与其通信。服务工作线程定义了第三种类型。[SW]
全局作用域是工作线程的“内部”。
WorkerGlobalScope
通用接口所有当前引擎都支持。
WorkerGlobalScope
作为特定类型的worker全局作用域对象的基类,包括DedicatedWorkerGlobalScope
、SharedWorkerGlobalScope
和ServiceWorkerGlobalScope
。
workerGlobal.self
workerGlobal.location
WorkerLocation
对象。workerGlobal.
WorkerNavigator
对象。workerGlobal.importScripts(...urls)
以下是事件处理程序(及其对应的事件处理程序事件类型),作为事件处理程序 IDL 属性,由实现WorkerGlobalScope
接口的对象支持。
事件处理程序 | 事件处理程序事件类型 |
---|---|
onerror 所有当前引擎都支持。 Firefox3.5+Safari4+Chrome4+ Opera11.5+Edge79+ Edge(旧版)12+Internet Explorer10+ Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android? | error
|
onlanguagechange WorkerGlobalScope/languagechange_event 所有当前引擎都支持。 Firefox74+Safari4+Chrome4+ Opera11.5+Edge79+ Edge(旧版)?Internet Explorer不支持 Firefox Android?Safari iOS5+Chrome Android?WebView Android37+Samsung Internet?Opera Android? | languagechange
|
onoffline WorkerGlobalScope/offline_event Firefox29+Safari8+Chrome否 Opera?Edge否 Edge(旧版)?Internet Explorer不支持 Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android? | offline
|
ononline WorkerGlobalScope/online_event Firefox29+Safari8+Chrome否 Opera?Edge否 Edge(旧版)?Internet Explorer不支持 Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android? | online
|
onrejectionhandled | rejectionhandled
|
onunhandledrejection | unhandledrejection
|
DedicatedWorkerGlobalScope
接口所有当前引擎都支持。
DedicatedWorkerGlobalScope
对象的行为就像它们有一个与之关联的隐式MessagePort
一样。此端口是创建工作线程时设置的通道的一部分,但它不会公开。
dedicatedWorkerGlobal.name
返回dedicatedWorkerGlobal的名称,即提供给Worker
构造函数的值。主要用于调试。
dedicatedWorkerGlobal.postMessage(message [, transfer ])
dedicatedWorkerGlobal.postMessage(message [, { transfer } ])
克隆message并将其传输到与dedicatedWorkerGlobal关联的Worker
对象。transfer可以作为要传输而不是克隆的对象列表传递。
dedicatedWorkerGlobal.close()
中止dedicatedWorkerGlobal。
以下是事件处理程序(及其对应的事件处理程序事件类型),作为事件处理程序 IDL 属性,由实现DedicatedWorkerGlobalScope
接口的对象支持。
事件处理程序 | 事件处理程序事件类型 |
---|---|
onmessage DedicatedWorkerGlobalScope/message_event 所有当前引擎都支持。 Firefox3.5+Safari4+Chrome4+ Opera10.6+Edge79+ Edge(旧版)12+Internet Explorer10+ Firefox Android?Safari iOS5+Chrome Android?WebView Android37+Samsung Internet?Opera Android11.5+ | message
|
onmessageerror DedicatedWorkerGlobalScope/messageerror_event 所有当前引擎都支持。 Firefox57+Safari16.4+Chrome60+ Opera?Edge79+ Edge (Legacy)18Internet Explorer否 Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android47+ | messageerror
|
SharedWorkerGlobalScope
接口所有当前引擎都支持。
共享工作线程通过其SharedWorkerGlobalScope
对象上的每个连接的connect
事件接收消息端口。
sharedWorkerGlobal 的
返回 sharedWorkerGlobal 的 name,即传递给 SharedWorker
构造函数的值。多个 SharedWorker
对象可以通过重用相同的名称对应于同一个共享工作线程(以及 SharedWorkerGlobalScope
)。
sharedWorkerGlobal 的
()中止 sharedWorkerGlobal。
以下是作为 事件处理程序 IDL 属性,由实现 SharedWorkerGlobalScope
接口的对象支持的 事件处理程序(及其对应的 事件处理程序事件类型)。
事件处理程序 | 事件处理程序事件类型 |
---|---|
onconnect SharedWorkerGlobalScope/connect_event 所有当前引擎都支持。 Firefox29+Safari16+Chrome4+ Opera10.6+Edge79+ Edge(旧版)?Internet Explorer不支持 Firefox Android?Safari iOS16+Chrome Android?WebView Android37+Samsung Internet?Opera Android11+ | connect
|
一个 工作线程事件循环的 任务队列仅包含事件、回调和网络活动作为 任务。
每个 WorkerGlobalScope
对象都有一个 closing 标志,最初为 false,但在 请求工作线程关闭时可以设置为 true。
一旦 WorkerGlobalScope
的 closing 标志设置为 true,事件循环的 任务队列将丢弃任何进一步添加到其中的 任务(队列中已有的任务不受影响,除非另有说明)。实际上,一旦 closing 标志为 true,计时器将停止触发,所有挂起的后台操作的通知都将被丢弃,等等。
每当工作线程的某个脚本中发生未捕获的运行时脚本错误时,如果该错误不是在处理先前的脚本错误期间发生的,则用户代理将为工作线程的 WorkerGlobalScope
对象 报告该错误。
Worker
和 SharedWorker
上都存在的属性以下是作为 事件处理程序 IDL 属性,由 Worker
和 SharedWorker
对象支持的 事件处理程序(及其对应的 事件处理程序事件类型)。
事件处理程序 | 事件处理程序事件类型 |
---|---|
onerror 所有当前引擎都支持。 Firefox44+Safari11.1+Chrome40+ Opera?Edge79+ Edge (旧版)17+Internet Explorer不支持 Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android? 所有当前引擎都支持。 Firefox29+Safari16+Chrome5+ Opera10.6+Edge79+ Edge(旧版)?Internet Explorer不支持 Firefox Android33+Safari iOS16+Chrome Android不支持WebView Android?Samsung Internet4.0–5.0Opera Android11–14 所有当前引擎都支持。 Firefox3.5+Safari4+Chrome4+ Opera10.6+Edge79+ Edge(旧版)12+Internet Explorer10+ Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android11+ | error
|
Worker
接口所有当前引擎都支持。
worker = new Worker(scriptURL [, options ])
返回一个新的 Worker
对象。scriptURL 将在后台获取并执行,创建一个新的全局环境,worker 代表该环境的通信通道。options 可用于通过 name
选项定义该全局环境的 name,主要用于调试目的。它还可以确保此新的全局环境支持 JavaScript 模块(指定 type: "module"
),如果指定了该选项,还可以用于通过 credentials
选项指定如何获取 scriptURL。
worker 的 terminate()
worker 的 postMessage(message [, transfer ])
worker 的 postMessage(message [, { transfer } ])
克隆 message 并将其传输到 worker 的全局环境。transfer 可以作为要传输而不是克隆的对象列表传递。
postMessage()
方法的第一个参数可以是结构化数据。
worker. postMessage({ opcode: 'activate' , device: 1938 , parameters: [ 23 , 102 ]});
以下是作为 事件处理程序 IDL 属性,由实现 Worker
接口的对象支持的 事件处理程序(及其对应的 事件处理程序事件类型)。
事件处理程序 | 事件处理程序事件类型 |
---|---|
onmessage | message
|
onmessageerror | messageerror
|
SharedWorker
接口所有当前引擎都支持。
sharedWorker = new
(scriptURL [, name ])返回一个新的 SharedWorker
对象。scriptURL 将在后台获取并执行,创建一个新的全局环境,sharedWorker 代表该环境的通信通道。name 可用于定义该全局环境的 name。
sharedWorker = new SharedWorker(scriptURL [, options ])
返回一个新的 SharedWorker
对象。scriptURL 将在后台获取并执行,创建一个新的全局环境,sharedWorker 代表该环境的通信通道。options 可用于通过 name
选项定义该全局环境的 name。它还可以确保此新的全局环境支持 JavaScript 模块(指定 type: "module"
),如果指定了该选项,还可以用于通过 credentials
选项指定如何获取 scriptURL。请注意,尝试使用 options(其 type
或 credentials
值与现有共享工作线程不匹配)构造共享工作线程将导致返回的 sharedWorker 触发错误事件,并且不会连接到现有的共享工作线程。
sharedWorker 的
返回 sharedWorker 的 MessagePort
对象,该对象可用于与全局环境通信。
self 的 navigator 的
返回用户代理可能可用的逻辑处理器数量。
WorkerNavigator
接口所有当前引擎都支持。
WorkerNavigator
接口实现了 Navigator
接口的一个子集,包括以下 API
WorkerLocation
接口所有当前引擎都支持。
所有当前引擎都支持。
WorkerLocation
接口类似于 Location
接口,但缺少 assign()
、replace()
、reload()
和 ancestorOrigins
成员。