在Web开发的时候经常会遇到浏览器不响应事件进入假死状态,甚至弹出“脚本运行时间过长“的提示框,如果出现这种情况说明你的脚本已经失控了。

背景

  • 一个浏览器打开的一个网页至少存在三个线程:js引擎线程(处理js)、GUI渲染线程(渲染页面)、浏览器事件触发线程(控制交互)。

    1:JavaScript引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来然后加以处理,浏览器无论再什么时候都只有一个JS线程在运行JS程序。

    2:GUI 渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

    3:事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。

  • 了解了浏览器的内核处理方式就不难理解浏览器为什么会进入假死状态了,当一段JS脚本长时间占用着处理机就会挂起浏览器的GUI更新,而后面的事件响应也被排在队列中得不到处理,从而造成了浏览器被锁定进入假死状态。另外JS脚本中进行了DOM操作,一旦JS调用结束就会马上进行一次GUI渲染,然后才开始执行下一个任务,所以JS中大量的DOM操作也会导致事件响应缓慢甚至真正卡死浏览器,如在IE6下一次插入大量的HTML。而如果真的弹出了“脚本运行时间过长“的提示框则说明你的JS脚本肯定有死循环或者进行过深的递归操作了。

  • 现在如果遇到了这种情况,我们可以做的不仅仅是优化代码,html5的webWorkers提供了js的后台处理线程的API,它允许将复杂耗时的单纯js逻辑处理放在浏览器后台线程中进行处理,让js线程不阻塞UI线程的渲染。这个线程不能和页面进行交互,如获取元素、alert等。多个线程间也是可以通过相同的方法进行数据传递。

web worker的使用

  1. <!DOCTYPE HTML>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>webworkers--calculate</title>
  6. </head>
  7. <body>
  8. <input id="num" name="num" type="text"/>
  9. <button onclick = "calculate()">计算</button>
  10. <br />
  11. <div id="result" style="color:red;"></div>
  12. <div id="time" style="color:red;"></div>
  13. <script type="text/javascript" src="calculate.js"></script>
  14. <script type="text/javascript">
  15. var worker = new Worker("calculate.js"); // 开启了一个后台线程,用来执行calculate.js
  16. var data1 =0;
  17. var data2 =0;
  18. worker.onmessage = function(event){
  19. var data = event.data;
  20. data2 = new Date().getTime();
  21. document.getElementById("result").innerHTML ="计算结果:"+data;
  22. document.getElementById("time").innerHTML ="workers 耗时:"+ (data2 - data1)+"ms";
  23. };
  24. function calculate(){
  25. data1 = new Date().getTime();
  26. var num = document.getElementById("num").value;
  27. var val = parseInt(num,10);
  28. worker.postMessage(val);
  29. }
  30. </script>
  31. </body>
  32. </html>
  1. onmessage = function(event){
  2. var num = event.data;
  3. var result = 0;
  4. //这个for循环执行了大量次数的运算
  5. for(var i = 0; i<num;i++){
  6. result += i;
  7. }
  8. // 计算结束后,通过postMessage将结果发给主线程;而主线程有个onmessage的回调可以接收这个结果
  9. postMessage(result);
  10. };

了解一下就可以了。这个api在未来可能用的多,但现在用的不多。
常见面试题场景,客户端js可不可以多线程? => 其实想问的是你知道WebWorker不?

标准答案:

简单讲一下:html5新增了WebWorker,可以通过new Worker(‘js脚本’)单独开一条线程,可以用来做大量的计算,并且不占据主线程的资源;与主线程通过postMessage这个api进行通信;
要注意 同一时间只能new 一个 Worker。