There is no doubt that JavaScript 是没有多线程之说的,他只能一件事一件事的做,做完一件事再做下一件事,假如你的 js 要花一段比较长的时间做一件事的话,那么浏览器将会卡顿一段时间,不对用户的操作产生响应,这可咋办呢?谢天谢地,html5 为我们提供了实现多线程的机制,这么好的东西,想必你早就再用了,不过没关系啊,咱们一块儿复习一下咯!
1、方法介绍
(1)构造函数 new Worker(arg) : 参数表示你的线程要执行的代码所在的 js 文件,例如'myworker.js', 构造函数当然是返回一个 Worker 类的实例
(2)worker.postMessage(message): 这个方法表示从主线程向子线程发送消息或者子线程向主线程发送消息,message 一般是一个字符串,也可以将一个 js 对象转成字符串发过去
(3)worker 上还有一个 message 事件,当有人向这个 worker 实例发送消息时,该事件被触发,我们可以从他的事件对象的 data 属性中获得 post 过来的值
可以看到 Woker 类的 API 是相当简洁的,只有两个最常用的方法,一个事件,下面我们来通过实际的例子看看。
- //main.html
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>main</title>
- </head>
- <body>
- <div id="out"></div>
- <input type="text" name="" id="txt">
- <button id="btn">发送</button>
- <script type="text/javascript">
- var out = document.getElementById("out");
- var btn = document.getElementById("btn");
- var txt = document.getElementById("txt");
- var worker = new Worker("thread1.js");
- btn.addEventListener("click",function(){
- var postData = txt.value;
- worker.postMessage(postData);
- },false);
- worker.addEventListener('message',function(e){
- out.innerText = e.data;
- },false);
- </script>
- </body>
- </html>
- //thread1.js
- onmessage =function(event){
- varres = event.data+"帅气!";
- postMessage(res);
- }
当我在文本框输入 "大~熊" 点击发送按钮就会出现如下效果
简单分析分析,我在主线程由 thead1.js 创建了一个 Worker 的实例 worker,当点击按钮时会调用他的 postMessage 方法,将文本框中的内容发送到 thread1.js,我们的 thread1.js 怎么做的呢?是这样,他侦听 message 事件,主线程发送消息过来就触发这个事件,执行回调函数,回调函数从事件对象取得发送来的值,然后将这个值加上 "帅气!", 然后在发送回去。主线程呢也侦听了 worker 的 message 事件,所以有消息过去时会触发,将消息内容显示在 div 中,如此就看到了上面的效果。
或许你会将这有什么用呢?这里确实没什么用,这里我们大可以在主线程还总完成加 "帅气!" 的操作,因为他的复杂度为 O(1)(哈哈,最近在学算法!),但是假如不是做这么简单的操作呢?这种方法的好处就是不过你的子线程做多么复杂的工作,都不会让主线程停下来,主线程改干嘛还干嘛,等到子线程把数据处理好了他直接拿过来就好了。
陆老师将可以在子线程中在调用 new Worker() 创建新的子线程,我发现这样是不可以的,会报 undefined 错误,也就是说子线程中是不能调用 Worker 构造函数的,一开始以为是自己错了,后来查阅了官方文档,发现也没有相关的描述。
下面看一个在主线程调用多个子线程的例子:
- //main.htmlDOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>main</title>
- </head>
- <body>
- <div id="out"></div>
- <input type="text" name="" id="txt">
- <button id="btn">发送</button>
- <script type="text/javascript">
- var out = document.getElementById("out");
- var btn = document.getElementById("btn");
- var txt = document.getElementById("txt");
- var worker1 = new Worker("thread1.js");
- var worker2 = new Worker("thread2.js");
- btn.addEventListener("click",function(){
- var postData = txt.value;
- worker1.postMessage(postData);
- },false);
- worker1.addEventListener('message',function(e){
- worker2.postMessage(e.data);
- },false);
- worker2.addEventListener('message',function(e){
- out.innerText = e.data;
- },false);
- </script>
- </body>
- </html>
- //thread1.js
- onmessage =function(event){
- varres = event.data+"帅气!";
- postMessage(res);
- }
- //thread2.jsonmessage =function(event){
- varres = event.data+"没骗你哟!";
- postMessage(res);
- close();
- }
主线程要完成一个任务需要两个线程,它创建了两个线程 worker1,2, 先向 worker1 请求,得到返回的数据后,再请求 worker2, 同时将 worker1 处理之后的数据交给 worder2 处理,然后拿到最终结果,显示在页面上。
在子线程中可以引入其他的 js 文件然后调用,比如下边这个例子。
- //main.html
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>main</title>
- </head>
- <body>
- <div id="out"></div>
- <input type="text" name="" id="txt">
- <button id="btn">发送</button>
- <script type="text/javascript">
- var out = document.getElementById("out");
- var btn = document.getElementById("btn");
- var txt = document.getElementById("txt");
- var worker1 = new Worker("thread1.js");
- btn.addEventListener("click",function(){
- var postData = txt.value;
- worker1.postMessage(postData);
- },false);
- worker1.addEventListener('message',function(e){
- out.innerText = e.data;
- },false);
- </script>
- </body>
- </html>
- //thread1.js
- importScripts('tools.js')
- onmessage = function(event){
- var res = handler(event.data);
- postMessage(res);
- }
- //tools.js
- function handler(data){
- return data+"加油加油!"
- }
可以看到我们的 thread1.js 并没有一个叫做 tools.js 的文件,但是它通过 importScripts() 导入了一个 js 文件,然后就可以调用里边暴露出来的方法了。
SharedWorker 的实质在于 share,不同的线程可以共享一个线程,他们的数据也是共享的。
直接用例子来探讨。
使用方法一:
- //main.html
- <!DOCTYPE HTML>
- <head>
- <title>Shared workers: demo 1</title>
- </head>
- <body>
- <div id="log"></div>
- <script>
- var worker = new SharedWorker('shared.js');
- var log = document.getElementById('log');
- worker.port.onmessage = function(e) { // note: not worker.onmessage!
- log.textContent += '\n' + e.data;
- }
- </script>
- </body>
- </html>
- //shared.js
- onconnect = function(e) {
- var port = e.ports[0];
- port.postMessage('Hello World!');
- }
这是从 w3c 拿得一个例子,下面先看第二种方法,再做分析
- <!DOCTYPE HTML>
- <html>
- <head>
- <title>
- Shared workers: demo 2
- </title>
- </head>
- <body>
- <div id="log">
- </div>
- <script>
- var worker = new SharedWorker('shared.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>
- </body>
- </html>
- //shared
- 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
- }
- }
第一种方法中是使用事件句柄的方式将听 message 事件,不需要调用 worker.port.start(), 第二种方法是通过 addEventListener() 方法监听 message 事件,需要 worker.port.start() 方法激活端口。他们不同于 worker,当有人和他通信时触发 connect 事件,然后他的 message 事件是绑定在 messagePort 对象上的,不想 worker 那样,你可以回头看看 worker 是怎么做的。
那么 sharedWorker 是怎么共享数据的呢?请看下面的例子。
- //main1 和main2都是这样
- <!DOCTYPE HTML>
- <html>
- <head>
- <title>Shared workers: demo 2</title>
- </head>
- <body>
- <div id="log"></div>
- <input type="text" name="" id="txt">
- <button id="get">get</button>
- <button id="set">set</button>
- <script>
- var worker = new SharedWorker('shared.js');
- var get = document.getElementById('get');
- var set = document.getElementById('set');
- var txt = document.getElementById('txt');
- var log = document.getElementById('log');
- worker.port.addEventListener('message', function(e) {
- log.innerText = e.data;
- }, false);
- worker.port.start(); // note: need this when using addEventListener
- set.addEventListener('click',function(e){
- worker.port.postMessage(txt.value);
- },false);
- get.addEventListener('click',function(e){
- worker.port.postMessage('get');
- },false);
- </script>
- </body>
- </html>
- //shared
- var data;
- onconnect = function(e) {
- var port = e.ports[0];
- port.onmessage = function(e) {
- if(e.data=='get'){
- port.postMessage(data);
- }else{
- data=e.data;
- }
- }
- }
这里分析一波,我们在 main1.html 的文本框输入数据,点击 set,然后在 main2.html 中点击 get 法现能够获取到我们在 main1.html 中设置的数据,这说明我们的 sharedWorker 事实上是单例的,就像 java 中的 static 类一样,不管 new 多少个,实际上只有一个,这样我们的不同线程就可以共享到 sharedWorker 中的数据了。这里把图给上,记得有篇文章没给图,然后有人给我建议了,问能不能给图。
最后来小结一下,worker 和 sharedWorker 没有什么悬糊的,就是把台前的工作搬到幕后去做,不打断台前的工作。正所谓台上十分钟,台下十年功,如果你把台下的十年供放到台上做,观众的唾沫星子早就把你淹死了,所以说那些费事费力的工作还是放到台下去,台上只用展示你最好的一面的好了,十分钟足以!
最最后,如果您觉得我的文章对您有所帮助的话,不妨关注一下我,您的支持是对我最大的鼓励。
参考:
html5 与 CSS3 权威指南上册(陆)
https://www.w3.org/TR/2015/WD-workers-20150924/#dedicated-workers-and-the-dedicatedworkerglobalscope-interface
来源: http://www.cnblogs.com/floor/p/6720261.html