JS 自然属于单线程环境,也就是说不能同时运行多个脚本。为什么这样?由于JS的设计目标是实现网页和用户交互,试想当用户点击页面,操做 DOM 时,有两个线程同时操做了DOM,那么以谁的结果为准呢?最坏的状况一个线程须要给一个元素添加内容,另外一个线程却把元素删除了,这将致使用户没法和页面交互。这就是JS只能单线程的缘由,全部任务都在一个线程上执行,没必要考虑多线程的问题。JS 须要同时执行两个任务的地方,可经过定时器、事件处理器等异步技术实现并行(其实依然单线程)。HTML5 引入 web worker 实现真正的多线程。经过 Web Worker 在后台执行一些操做,例如触发长时间运行的脚本以处理计算密集型任务,同时却不会阻碍 UI 或其余脚本处理用户互动。 Worker 利用相似线程的消息传递实现并行。这很是适合您确保对 UI 的刷新、性能以及对用户的响应。javascript
两种类型:html
worker 和主线程之间通讯经过消息机制进行--使用postMessage
函数向对方传递数据,对方经过监听message
事件获取并处理数据。数据不是共享,而是复制。因此传递对象时,能够操做该对象,而不会影响另外一个线程中的对象。html5
worker 和主线程的脚本必须同源,简单理解成同一个网站加载到浏览中。java
在主线程检测浏览器是否支持 worker,在决定是否建立 worker。web
if(window.worker){
//do something
}
复制代码
目前主要浏览器都支持。更多详情数据库
web worker 在独立线程中运行,可将其代码保存在一个单独的JS文件中,而后在建立 worker 时引入该脚本。不少浏览器还不支持本地文件运行worker,咱们须要结合服务器时间。可以使用 express 建立一个服务器。express
在 public/javascripts 文件夹在建立 worker.js:canvas
this.addEventListener('message', (event) => {
console.log(this)
console.log(event)
console.log(event.data)
console.log(event.origin)
console.log(event.ports)
console.log(event.source)
console.log(event.lastEventId)
self.postMessage({ name: 'worker.js', data: 'hello,main.js!' })
}, false)
复制代码
这就是 worker 线程的代码。api
在页面的 JS 代码中建立 worker,为了简单起见,在script 标签中写脚本:数组
<script> let worker = new Worker('/javascripts/worker.js');//建立一个 worker let workerButton = document.getElementByI('workerButton'); workerButton.addEventListener('click', (event)=> { console.log(event) worker.postMessage({ name: 'main.js', data:'hello,worker.js!' });//向 worker 线程发送消息 }, false) // 监听 worker 线程发送过来的消息 worker.addEventListener('message', (event) => { console.log(event.data); }, false) </script>
复制代码
建立 worker 时指定的脚本是异步加载的,若是加载成功,会生成一个 worker 线程。彻底加载和执行以前,系统很差生成 worker。若是脚本不存在,返回404,建立 worker失败。
postMessage 用来启动 worker,也用来发送消息。
在主线程中,经过worker.terminate()
中止 worker;在worker 线程中,使用close()
来中止worker。
let stopWorker = document.getElementById('stopWorker');
stopWorker.addEventListener('click', () => {
let result = worker.terminate();
console.log(result);
}, false);
复制代码
worker 本身中止:
在主线程中通知worker中止:
let stopWorker = document.getElementById('stopWorker');
stopWorker.addEventListener('click', () => {
let result = worker.postMessage('stop');
console.log(result);//undefined
}, false)
复制代码
worker.js
this.addEventListener('message', (event) => {
console.log(this)
console.log(event)
console.log(event.data)
console.log(event.origin)
console.log('cache',this.cache)
console.log(event.source)
console.log(event.lastEventId)
if ('stop' === event.data) {
let result = this.close();
console.log('worker stop ',result);//undefined
}
self.postMessage({ name: 'worker.js', data: 'hello,main.js!' })
}, false)
复制代码
worker 做用域:在worker脚本文件内,this 和 self 都是全局做用域。上面的console.log(this)
的输出是:
DedicatedWorkerGlobalScope。
复制代码
worker 线程中并不能完成使用主线程的功能,只能使用部分JS功能:
worker 中没法使用:
咱们在 worker.js 发发送一个http 请求,拿到返回值后传递给主线程:
复制代码
可以使用importScripts
函数加载外部的脚本进入 worker 脚本执行。
在 worker.js 记载一个脚本:
importScripts('./importTest.js');// 可传递多个路径,路径是相对于 worker.js 的
复制代码
importTest.js
test(5)
function test(time) {
setInterval(() => {
console.log('你好');
}, 1000 * time);
复制代码
worker 线程中止后,加载的外部脚本也中止执行。
可在主线程中处理 worker 线程的错误,在主线程中监听 worker 的 错误事件:
worker.addEventListener('error', (event) => {
console.log(event.colno);
console.log(event.filename);
console.log(event.message);
}, false);
复制代码
输出该错误事件,可看到事件的一些属性: 不冒泡、可取消等,具备普通事件的一些属性。
worker 生成一个错误:
this.addEventListener('message', (event) => {
if ('stop' === event.data) {
let result = this.close();
console.log('worker stop ', result);
}
self.postMessage({ name: 'worker.js', data: 'hello,main.js!' })
this.postMessage(new Error('制造一个错误!'));
}, false)
复制代码
上面的例子,将 worker 代码放在单独的文件里,这样便于管理代码,也方便修改,由于有语法高亮,能够将 worker 代码放在和主线程相关的html文件里:
<script id="worker" type="javascript/worker"> this.addEventListener('message', (event) => { if ('stop' === event.data) { this.close(); } console.log(event.data); self.postMessage({ name: 'worker.js', data: 'hello,main.js!' }); }, false); </script>
<script> let workerContent = document.getElementById('worker').textContent; let blob = new Blob([workerContent], { type: 'text/javacript' }); let url = URL.createObjectURL(blob); console.log(url); let worker = new Worker(url); worker.addEventListener('message', (event) => { console.log(event.data); }); let workerButton = document.querySelector('#workerButton'); workerButton.addEventListener('click', (event) => { worker.postMessage({ name: "主线程" }); }, false); </script>
复制代码
将 worker 的代码在 script 标签中,声明类型为javascript/worker
,就不会被浏览器解析成JS代码,而是当成普通的 html 标签,可经过DOM api 获取标签内的文本,建立 worker。 使用 Blob 生成一个blob 对象,再使用 URL.createObjectURL成链接,再生成 worker。
let blob = new Blob([workerContent], { type: 'text/javacript' });
let url = URL.createObjectURL(blob);
console.log(url);
let worker = new Worker(url);
复制代码
经常使用的场景: