因为下个项目中要用到一些倒计时的功能,所以就提前准备了一下,省的到时候出现一下界面不友好和一些其他的事情。正好趁着这个机会也加深一下html5中的多线程worker的用法和理解。
Worker简介
JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。这些都是我们所公知的。但是随着业务的不断增加,只是单纯的单线程模式已经可能无法满足我们的需求了。于是在html5中新增了后台任务worker API。
w3c中的介绍:web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行。
worker就是为了JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。开启后台线程,在不影响前台线程的前提下做一些耗时或者异步的操作。因为是不同的线程,所以主线程与worker线程互不干扰。也不会相互打断。所以在一些场景可以提高页面的流程性。Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
使用规则
- 必须同源:也就是说js文件的路径必须和主线程的脚本同源。防止了外部引用。
- dom限制:在worker线程中不能操作dom(document,window,parent)。注意可以使用浏览器的navigator和location对象。
- 通讯限制:worker线程和主线程不在一个上下文中所以不能直接通讯。也就是说主线程定义的变量在worker中也是不能使用的。所有只能通过消息完成。
- 提示禁止:worker线程不能alert和confirm,这个不知到具体原因?
- 传值dom:进行消息通讯也不能传值dom只能是变量。
- ie限制:ie9不能使用!ie9不能使用!ie9不能使用!
worker文档
Web Workers API的Worker
界面代表了一个可以轻松创建的后台任务,可以将消息发送回其创建者。创建worker就像调用 构造函数并指定要在工作线程中运行的脚本一样简单。
构造函数
worker():创建一个专用的Web worker,在指定的URL上执行脚本。示例:var worker=new Worker('js/setTime.js');
属性
onerror:
这是一个在error事件发生时调用的函数,并且通过该函数冒泡worker。示例:worker.onerror=function(){....};
onmessage:
这是一个worker中message事件要发生的时候调用的事件。 示例:worker.onmessage=function(){....};
这个事件一般与postMessage事件同时使用,一个用来发送数据,一个用来接受数据。例如:
主线程中:
var jsId = "00001"; var worker = new Worker('js/setTime.js'); worker.postMessage(jsId);
worker线程中:
//接受事件参数 onmessage = function(e) { console.log(e.data[0]) }
这样就完成了一个主线程向worker线程传递参数的过程。同样如果worker线程要向主线程传递参数反过来写即可。
onmessageerror:
在消息传递过程出现错误的属性事件。示例:worker.onmessageerror=function(){....};
方法
postMessage:
向线程worker的内部范围发送消息,可以设置参数,发送给worker线程的数据。在onmessage中接受。
terminate:
过多的开启worker线程非常浪费资源所以在使用过后可以终止它,终止方法使用terminate()。示例:worker.terminate();
close:
除了上面的关闭,如果是在worker线程自身也可以使用self.close()关闭。
计时器示例
上面说了那么多都是介绍worker的一些基本属性或者方法的使用。下面通过具体的示例来看效果。
我们就拿最常用的倒计时来做示例说明。很简单的一个例子。我们在业务中经常遇到倒计时业务,在倒计时的时候还要做一些其他的业务。因为js单线程的特性,你会发现你的倒计时在你进行其他业务操作的时候是暂停了的。例如现在是9:57你进行了三秒的业务处理。等业务处理完成应该是:9:54,但是你的倒计时还是9:57.就很明显的说明了这一个现象。
场景业务设计
那么我们现在设计这么一个业务操作,
- 首先我们页面有一个定时器和一个业务操作按钮(用来模拟耗时的操作)。
- 然后把定时器写到一个worker中进行倒计时操作。
- 最后通过消息通讯把每次的倒计时时间发送给主线程让主线程修改显示时间。
- 结束倒计时完毕结束定时器和线程
有人可能会说为什么还要回到主线程修改时间显示值,请看一下上面的使用规则,我本来也是打算进行主线程传值dom给worker线程奈何不行只能在回传回来。
代码展示
Html代码:
<body> <div> <span id="Minute_p">10</span> : <span id="Second_p">00</span> </div> <button type="button" onclick="business()">耗时操作</button>
</body>
主线程js代码:
//页面加载完成后初始化 window.onload = function() { //创建定时器线程 var worker = new Worker('js/setTime.js'); //获取dom对象 var domMinute_p = document.getElementById('Minute_p'); var domSecond_p = document.getElementById('Second_p'); worker.postMessage(600); //这里可以接受worker线程的返回值 worker.onmessage = function(event) { var totalSecond = event.data; console.log(totalSecond) //计算分钟数 var minute_p = parseInt(totalSecond / 60); domMinute_p.innerText = minute_p; //计算秒数 var second_p = parseInt(totalSecond % 60); domSecond_p.innerText = second_p; } } //这里是模拟的耗时操作 function business() { var data = [1, 2, 3, 4, 5]; for(var i = 1; i < 1000; i++) { for(var j = 1; j < 1000; j++) { for(var k = 1; k < 5000; k++) { var b = k * 100; } } } console.log("业务终于走完了!") }
worker线程js代码:
var totalSecond = 600; var domMinute_p, domSecond_p, //接受事件参数 onmessage = function(e) { console.log(e.data) domMinute_p = e.data; } var timeId = setInterval(function() { totalSecond--; if(totalSecond == 0) { self.close(); } console.log(totalSecond) postMessage(totalSecond) }, 1000)
好了大致示例就是这么多。下面是截图效果:
开始运行后编号1会开始倒计时,但是当你点击了编号2进行了模拟耗时后,编号1还是会卡住,只有完成编号2后才会运行,但是不同与上面说到的单线程是,他再次运行时的时间是正确时间,还是刚才的例子如果是9:57,点击编号2模拟耗时了3秒,耗时完成后编号1会显示9:54而不是单线程的9:57。就说明worker现在在耗时操作的时候是持续运行的,时间卡只不过是主线程的dom操作被卡住了而已(可以把耗时业务也开启worker就不卡住了)。这里只是介绍worker的使用,所有就不纠结这个界面显示的问题。
补充界面显示方法:
后来有些人就问我怎弄界面显示,我还是真的心疼你们啊,不知道举一反三吗,当然是吧业务耗时也放到后台线程啊,哈哈!!!
再特此说明一个问题,仅在安卓测试:就是定时器在息屏模式下仍继续执行
我还是上面的例子做个例子:
把耗时业务放到business.js文件
onmessage = function(e) { console.log(e.data) for(var i = 1; i < 1000; i++) { for(var j = 1; j < 1000; j++) { for(var k = 1; k < 5000; k++) { var b = k * 100; } } } console.log("耗时业务走完了"); postMessage(1) }
然后主文件js调用就好了啊:
//这里是模拟的耗时操作 function business() { var worker = new Worker('js/business.js'); worker.postMessage("开启任务耗时"); worker.onmessage = function(event) { if(event == 1) { console.log("点击一次完成") worker.terminate() } } }
截图效果: