Web Workers - (Worker(专有) and SharedWorker(共享))
- Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法
- 线程可以执行任务而不干扰用户界面
- 可以使用XMLHttpRequest执行 I/O (尽管responseXML和channel属性总是为空)
- 一个worker 可以将消息发送到创建它的JavaScript代码, 通过将消息发布到该代码指定的事件处理程序
——————————————————————————————————————————
Web Workers API
- 一个worker是使用一个构造函数创建的一个对象(e.g. Worker()) 运行一个命名的JavaScript文件
- workers 运行在另一个全局上下文中,不同于当前的window
- 使用 window快捷方式获取当前全局的范围 (而不是self) 在一个 Worker 内将返回错误。
- DedicatedWorkerGlobalScope 对象代表了专用worker的上下文
- 专用workers是指标准worker仅在单一脚本中被使用,一个专用worker仅仅能被首次生成它的脚本使用
- 共享worker的上下文是SharedWorkerGlobalScope对象,共享worker可以同时被多个脚本使用。
- 在worker内,不能直接操作DOM节点,也不能使用window对象的默认方法和属性。
- 可以使用包括WebSockets,IndexedDB等数据存储机制。参考
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers
- workers和主线程间的数据传递通过这样的消息机制进行——双方都使用postMessage()方法发送各自的消息,使用onmessage事件处理函数来响应消息(消息被包含在Message事件的data属性中)。
- 这个过程中数据并不是被共享而是被复制。
- 只要运行在同源的父页面中,workers可以依次生成新的workers
——————————————————————————————————————————
专用worker
worker特性检测
- 为了更好的错误处理控制以及向下兼容,将你的worker运行代码包裹在以下代码中是一个很好的想法
if (window.Worker) { ... }
生成一个专用worker
var myWorker = new Worker('worker.js');
专用worker中消息的接收和发送
- 在worker内部,worker是有效的全局作用域。
// 主线程中
// 发送
first.onchange = function() { // first是一个input元素
myWorker.postMessage([first.value]);
console.log('Message posted to worker');
}
// 接收
myWorker.onmessage = function(e) {
result.textContent = e.data; // result是个p元素
console.log('Message received from worker');
}
// worker.js 中
// 接收和发送
onmessage = function(e) {
console.log('Message received from main script');
var workerResult = 'Result: ' + (e.data[0] * 10);
console.log('Posting message back to main script');
postMessage(workerResult);
}
终止worker
- worker 线程会被立即杀死,不会有任何机会让它完成自己的操作或清理工作。
// 主线程中
myWorker.terminate();
- 而在worker线程中,workers 也可以调用自己的 close 方法进行关闭
close();
处理错误
- 当 worker 出现运行中错误时,它的 onerror 事件处理函数会被调用
- 会收到一个扩展了 ErrorEvent 接口的名为 error的事件
- 该事件不会冒泡但可以被取消
- 可以调用错误事件的 preventDefault()方法,防止触发默认动作
- 错误事件有以下三个用户关心的字段
- message 错误消息
- filename 发生错误的脚本文件名
- lineno 发生错误时所在脚本文件的行号。
生成subworker
- worker 能够生成更多的 worker。这就是所谓的subworker
- 必须托管在同源的父页面内
- subworker 解析 URI 时会相对于父 worker 的地址而不是自身页面的地址
引入脚本与库
- Worker 线程能够访问一个全局函数importScripts()来引入脚本,该函数接受0个或者多个URI作为参数来引入资源
- 脚本路径相对于创建当前Worker的window的域开始
importScripts(); /* 什么都不引入 */
importScripts('./foo.js'); /* 只引入 "foo.js" */
importScripts('foo.js', 'bar.js'); /* 引入两个脚本 */
- 如果脚本无法加载,将抛出 NETWORK_ERROR 异常
- importScripts() 之后的函数声明依然会被保留,因为它们始终会在其他代码之前运行。
- 脚本的下载顺序不固定,但执行时会按照传入 importScripts() 中的文件名顺序进行。这个过程是同步完成的;直到所有脚本都下载并运行完毕,importScripts() 才会返回。
——————————————————————————————————————————
共享worker
- 一个共享worker可以被多个脚本使用——即使这些脚本正在被不同的window、iframe或者worker访问。
- 如果共享worker可以被多个浏览上下文调用,所有这些浏览上下文必须属于同源(相同的协议,主机和端口号)。
生成一个共享worker
var myWorker = new SharedWorker('worker.js');
- 一个共享worker通信必须通过端口对象——一个确切的打开的端口供脚本与worker通信(在专用worker中这一部分是隐式进行的)。
- 在传递消息之前,端口连接必须被显式的打开,打开方式是使用onmessage事件处理函数或者start()方法。
- start()方法的调用只在一种情况下需要,那就是消息事件被addEventListener()方法使用。
// 在主线程中
// 直接给port绑定事件函数,不需要start方法
myWorker.port.onmessage = function(e) {
result1.textContent = e.data;
console.log('Message received from worker');
console.log(e.lastEventId);
}
// 使用start方法显示打开端口
myWorker.port.start(); // 父级线程中的调用
- onconnect事件在父级线程中,设置onmessage事件处理函数,或者显式调用start()方法时触发
// 在Worker中
onconnect = function(e) { // onconnect 已链接事件
var port = e.ports[0]; // 获取端口对象
// 直接给port绑定事件函数,不需要start方法
port.onmessage = function(e) {
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
port.postMessage(workerResult);
}
// 使用start方法显示打开端口
port.start(); // worker线程中的调用, 假设port变量代表一个端口
}
共享worker中消息的接收和发送
- postMessage() 方法必须被端口对象调用
squareNumber.onchange = function() { // squareNumber是一个input元素
myWorker.port.postMessage([squareNumber.value]);
console.log('Message posted to worker');
}
——————————————————————————----——————
关于线程安全
- 需要通过序列化对象来与线程交互特定的数据(好像postMessage已经自身实现了这个功能,大部分浏览器使用结构化拷贝来实现该特性。)
——————————————————————————————————————————
内容安全策略
- 如果document使用了内容安全策略头部
Content-Security-Policy: script-src 'self'
- 会禁止它内部包含的脚本代码使用eval()方法。
- 然而,如果脚本代码创建了一个worker,在worker上下文中执行的代码却是可以使用eval()的。
- 为了给worker指定内容安全策略,必须为发送worker代码的请求本身加上一个 内容安全策略。
- worker脚本的源如果是一个全局性的唯一的标识符(例如,它的URL指定了数据模式或者blob),worker则会继承创建它的document或者worker的CSP(Content security policy内容安全策略)。
——————————————————————————————————————————
worker中数据的接收与发送:详细介绍
- 拷贝而并非共享的那个值称为 消息
- 结构化拷贝算法可以接收JSON数据以及一些JSON不能表示的数据——比如循环引用。
传递数据的例子
- 通用异步 eval() 可以绕开主进程的内容安全策略
// 主进程中
var asyncEval = (function () {
// 用数组保存回调函数,用id标明对应code需要调用的执行函数在数组中的位置,应为postMseeage是异步的
var aListeners = []
// 它的URL指定了数据模式或者blob
var oParser = new Worker("data:text/javascript;charset=US-ASCII,onmessage%20%3D%20function%20%28oEvent%29%20%7B%0A%09postMessage%28%7B%0A%09%09%22id%22%3A%20oEvent.data.id%2C%0A%09%09%22evaluated%22%3A%20eval%28oEvent.data.code%29%0A%09%7D%29%3B%0A%7D");
oParser.onmessage = function (oEvent) {
if (aListeners[oEvent.data.id]) { aListeners[oEvent.data.id](oEvent.data.evaluated); }
delete aListeners[oEvent.data.id]; // 这种方式会导致数组长度不断延长,并不理想
};
// sCode:需要通过eval执行的字符串型代码
// fListener:代码执行结果的处理函数
return function (sCode, fListener) {
aListeners.push(fListener || null);
oParser.postMessage({
"id": aListeners.length - 1,
"code": sCode
});
};
})();
// Worker中(data URL 相当于一个网络请求,它有如下返回:)
onmessage = function(oEvent) {
postMessage({
'id': oEvent.data.id,
'evaluated': eval(oEvent.data.code)
});
}
// 主进程中运用
asyncEval("\"Hello World!!!\"", function (sHTML) {
document.body.appendChild(document.createTextNode(sHTML));
});
asyncEval("(function () {\n\tvar oReq = new XMLHttpRequest();\n\toReq.open(\"get\", \"http://www.mozilla.org/\", false);\n\toReq.send(null);\n\treturn oReq.responseText;\n})()");
- 封装一个Worker对象,方便为Worker中每个处理方法定义回调函数
- 可以指定调用内部的任意方法,并满足这些方法的传参需求
// 主进程
var oMyTask = new QueryableWorker("my_task.js");
oMyTask.addListener("printSomething", function (nResult) {
document.getElementById("firstLink").parentNode.appendChild(document.createTextNode(" The difference is " + nResult + "!"));
});
oMyTask.addListener("alertSomething", function (nDeltaT, sUnit) {
alert("Worker waited for " + nDeltaT + " " + sUnit + " :-)");
});
// sURL Worker的运行脚本地址
// fDefListener onmessage的默认回调函数
// fOnError Worker的错误回调函数
function QueryableWorker(sURL, fDefListener, fOnError) {
var oInstance = this
var oWorker = new Worker(sURL)
var oListeners = {};
this.defaultListener = fDefListener || function () { };
oWorker.onmessage = function (oEvent) {
// rnb93qh 内部定义的参数key,内部处理方法处理好数据后回传给主进程的参数
// vo42t30 内部定义的方法key,内部处理方法定义好的主进程处理方法,需要通过addListener添加
if (oEvent.data instanceof Object && oEvent.data.hasOwnProperty("vo42t30") && oEvent.data.hasOwnProperty("rnb93qh")) {
oListeners[oEvent.data.vo42t30].apply(oInstance, oEvent.data.rnb93qh);
} else {
this.defaultListener.call(oInstance, oEvent.data);
}
};
if (fOnError) { oWorker.onerror = fOnError; }
// 调用Worker中定义好的方法,每个方法都有对应的回调名,调用前需要先用addListener添加对应的回调函数
// 参数1:内部已有的方法名
// 参数2-n:传给该方法的参数,可以有多个
// 通过私有key bk4e1h0和ktp3fm1来确定内部定义好的方法
this.sendQuery = function () {
if (arguments.length < 1) { throw new TypeError("QueryableWorker.sendQuery - not enough arguments"); return; }
oWorker.postMessage({ "bk4e1h0": arguments[0], "ktp3fm1": Array.prototype.slice.call(arguments, 1) });
};
// 添加信息处理方法
this.addListener = function (sName, fListener) {
oListeners[sName] = fListener;
};
// 移除信息处理方法
this.removeListener = function (sName) {
delete oListeners[sName];
};
// 对象的一个方法用来发送信息,主要为了区分sendQuery,用来调用Worker中的默认处理方法
this.postMessage = function (vMsg) {
Worker.prototype.postMessage.call(oWorker, vMsg);
};
// 用来销毁Worker
this.terminate = function () {
Worker.prototype.terminate.call(oWorker);
};
};
// worker中
// worker中的处理方法集合和对应的主进程回调方法
var queryableFunctions = {
getDifference: function (nMinuend, nSubtrahend) {
reply("printSomething", nMinuend - nSubtrahend);
},
waitSomething: function () {
setTimeout(function () { reply("alertSomething", 3, "seconds"); }, 3000);
}
};
// 处理信息的默认方法
function defaultQuery(vMsg) {
}
// 回传信息给主进程
// vo42t30 标明主进程回调处理函数名
// rnb93qh 标明信息数组
function reply() {
if (arguments.length < 1) { throw new TypeError("reply - not enough arguments"); return; }
postMessage({ "vo42t30": arguments[0], "rnb93qh": Array.prototype.slice.call(arguments, 1) });
}
onmessage = function (oEvent) {
if (oEvent.data instanceof Object && oEvent.data.hasOwnProperty("bk4e1h0") && oEvent.data.hasOwnProperty("ktp3fm1")) {
// self 是Worker中的常量,指向当前上下文
queryableFunctions[oEvent.data.bk4e1h0].apply(self, oEvent.data.ktp3fm1);
} else {
defaultQuery(oEvent.data);
}
};
通过转让所有权(可转让对象)来传递数据
- Google Chrome 17 与 Firefox 18 包含另一种性能更高的方法来将特定类型的对象(可转让对象)
- 可转让对象从一个上下文转移到另一个上下文而不会经过任何拷贝操作。这意味着当传递大数据时会获得极大的性能提升。
- 一旦对象转让,那么它在原来上下文的那个版本将不复存在。该对象的所有权被转让到新的上下文内。
——————————————————————————----——————
嵌入式 worker
- 一个 <script> 元素没有 src 特性,并且它的 type 特性没有指定成一个可运行的 mime-type,那么它就会被认为是一个数据块元素,并且能够被 JavaScript 使用。
<script type="text/js-worker">
// 该脚本不会被 JS 引擎解析,因为它的 mime-type 是 text/js-worker。
var myVar = "Hello World!";
onmessage = function (oEvent) {
postMessage(myVar);
};
// 剩下的 worker 代码写到这里。
</script>
<script type="text/javascript">
// 该脚本会被 JS 引擎解析,因为它的 mime-type 是 text/javascript。
function pageLog (sMsg) {
// 使用 fragment:这样浏览器只会进行一次渲染/重排。
var oFragm = document.createDocumentFragment();
oFragm.appendChild(document.createTextNode(sMsg));
oFragm.appendChild(document.createElement("br"));
document.querySelector("#logDisplay").appendChild(oFragm);
}
// 在过去...:
// 我们使用 blob builder
// ...但是现在我们使用 Blob...:
var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});
// 创建一个新的 document.worker 属性,包含所有 "text/js-worker" 脚本。
document.worker = new Worker(window.URL.createObjectURL(blob));
document.worker.onmessage = function (oEvent) {
pageLog("Received: " + oEvent.data);
};
// 启动 worker.
window.onload = function() { document.worker.postMessage(""); };
</script>
- 将一个函数转换为blob,然后为这个blob生成URL对象
function fn2workerURL(fn) {
var blob = new Blob(['('+fn.toString()+')()'], {type: 'application/javascript'})
return URL.createObjectURL(blob)
}
——————————————————————————————————————————
更多示例
在后台执行运算
- worker 的一个优势在于能够执行处理器密集型的运算而不会阻塞 UI 线程。
- 实现传入一个斐波那契数位置,返回该位置的对应值
- 通过递归创建线程,来获取改位置最后由多少个1构成来实现。(该例子相当消耗性能,慎用)
// worker执行脚本中,文件名fibonacci.js
var results = [];
function resultReceiver(event) {
results.push(parseInt(event.data));
if (results.length == 2) {
postMessage(results[0] + results[1]);
}
}
function errorReceiver(event) {
throw event.data;
}
onmessage = function (event) {
var n = parseInt(event.data);
if (n == 0 || n == 1) {
postMessage(n);
return;
}
for (var i = 1; i <= 2; i++) {
var worker = new Worker("fibonacci.js");
worker.onmessage = resultReceiver;
worker.onerror = errorReceiver;
worker.postMessage(n - i);
}
};
// 主进程中
var worker = new Worker("fibonacci.js");
worker.onmessage = function (event) {
document.getElementById("result").textContent = event.data;
};
worker.onerror = function (error) {
throw error;
};
worker.postMessage(5);
划分任务给多个 worker
- 当多核系统流行开来,将复杂的运算任务分配给多个 worker 来运行已经变得十分有用,这些 worker 会在多处理器内核上运行这些任务。
——————————————————————————————————————————
其它类型的worker
- ServiceWorkers (服务worker)一般作为web应用程序、浏览器和网络(如果可用)之前的代理服务器。它们旨在(除开其他方面)创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动并更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步API。
- Audio Workers (音频worker)使得在web worker上下文中直接完成脚本化音频处理成为可能。
——————————————————————————————————————————
worker中可用的函数和接口
- Navigator 用户代理的状态和标识
- XMLHttpRequest
- Array, Date, Math, and String
- WindowTimers.setTimeout and WindowTimers.setInterval
Web Workers - (Worker(专有) and SharedWorker(共享))的更多相关文章
- 通过使用Web Workers,Web应用程序可以在独立于主线程的后台线程中,运行一个脚本操作。这样做的好处是可以在独立线程中执行费时的处理任务,从而允许主线程(通常是UI线程)不会因此被阻塞/放慢。
Web Workers API - Web API 接口参考 | MDNhttps://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API ...
- Web Workers文档
Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法.线程可以执行任务而不干扰用户界面.此外,他们可以使用XMLHttpRequest执行 I/O (尽管responseXML和 ...
- [书籍翻译] 《JavaScript并发编程》第五章 使用Web Workers
本文是我翻译<JavaScript Concurrency>书籍的第五章 使用Web Workers,该书主要以Promises.Generator.Web workers等技术来讲解Ja ...
- 深入web workers (上)
前段时间,为了优化某个有点复杂的功能,我采用了shared workers + indexDB,构建了一个高性能的多页面共享的服务.由于是第一次真正意义上的运用workers,比以前单纯的学习有更多体 ...
- html5 Web Workers
虽然在JavaScript中有setInterval和setTimeout函数使javaScript看起来好像使多线程执行,单实际上JavaScript使单线程的,一次只能做一件事情(关于JavaSc ...
- 3D拓扑自动布局之Web Workers篇
2D拓扑的应用在电信网管和电力SCADA领域早已习以为常了,随着OpenGL特别是WebGL技术的普及,3D方式的数据可视化也慢慢从佛殿神堂步入了寻常百姓家,似乎和最近高档会所被整改为普通茶馆是一样的 ...
- HTML5 Web Workers来加速您的移动Web应用
一直以来,Web 应用程序被局限在一个单线程世界中.这的确限制了开发人员在他们的代码中的作为,因为任何太复杂的东西都存在冻结应用程序 UI 的风险.通过将多线程引入 Web 应用程… 在本文中,您将使 ...
- (92)Wangdao.com_第二十五天_线程机制_H5 Web Workers 分线程任务_事件 Event
浏览器内核 支撑浏览器运行的最核心的程序 IE 浏览器内核 Trident内核,也是俗称的IE内核Chrome 浏览器内核 统称为 Chromium 内核或 ...
- JavaScript是如何工作的:Web Workers的构建块 + 5个使用他们的场景
摘要: 理解Web Workers. 原文:JavaScript是如何工作的:Web Workers的构建块 + 5个使用他们的场景 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 这 ...
随机推荐
- vue路由--命名视图
有时候想同时(同级)展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar(侧导航) 和 main(主内容) 两个视图,这个时候命名视图就派上用场了.你可以在界面中拥有多个单独命名的视图, ...
- js—数字那些事儿
进制之间互相转化 a=133 十进制转成其他进制 a.toString(num) a.toString(2); //转化成二进制,返回"10000101" a.toString(8 ...
- 嗅探、DNS劫持配合CS钓鱼
本章节讲述的是嗅探和DNS劫持的利用 嗅探:同一个局域网下,原本应该丢弃的包,被保留下来,即使不开双向欺骗 Driftnet工具:Driftnet监视网络流量,抓取网络流量中的JPEG和GIF图像.这 ...
- 静态SRAM芯片工作原理
下面谈谈当存储字节的过程是怎样的:下面的示意图显示的也仅仅是最简单状态下的情况,当内存条上仅剩一个RAM芯片的情况.对于X86处理器,它通过地址总线发出一个具有22位二进制数字的地址编码--其中11位 ...
- 装饰器(Python)
装饰器(decorators)是 Python 的一个重要部分.简单地说:装饰器是修改其他函数的功能的函数,能让我们的代码更容易被扩展,更加简短.举个例子: def login(): print(&q ...
- 使用JDBC分别利用Statement和PreparedStatement来对MySQL数据库进行简单的增删改查以及SQL注入的原理
一.MySQL数据库的下载及安装 https://www.mysql.com/ 点击DOWNLOADS,拉到页面底部,找到MySQL Community(GPL)Downloads,点击 选择下图中的 ...
- Spring Boot JPA分页 PageRequest报错
在使用Spring Boot JPA分页 PageRequest分页时,出现如下错误: 本来以为是包导入出现了问题,结果发现并不是.导入包如下: 后来在网上查找相关资料,发现这样的用法,好像也可以用, ...
- 转载整理:SublimeText3 Emmet失效问题以及win7 pyV8安装问题
SublimeText3 Emmet安装问题网上已经很多帖子了,这个简单,主要对win7 64位我本人遇到的Emmet好多快捷功能无法用(比如ul>li*4 Tab无法生成)问题做了整理!搜了 ...
- Bootstrap 手机屏幕自适应的响应式布局开关
head中添加 <meta name="viewport" content="width=device-width, initial-scale=1, shrink ...
- 浅谈mysql触发器
什么是触发器?简单的说,就是一张表发生了某件事(插入.删除.更新操作),然后自动触发了预先编写好的若干条SQL语句的执行.触发器本质也是存储过程,只是不需要手动调用,触发某事件时自动调用.触发器里的S ...