[关闭]
@fengfeng 2015-04-27T07:50:25.000000Z 字数 6866 阅读 2585

Browser Single-thread Mechanism 关于浏览器单线程机制

js 浏览器 single-thread Timer


browser single thread

The browser UI thread is responsible for both UI updates and JavaScript execution, Only one can happen at a time.


everything in the UI of your browser, including the page layout and style engine, the JS engine, the garbage collector, etc — all of that is on a single-thread, which means at any given moment, only one of the tasks can be processing

from 1. 2
浏览器的UI更新和js的执行共用同一个线程UI Thread,同一时间要么进行UI更新,要么执行js代码。他们通过共享同一个任务队列UI Queue来实现这一机制。
例子1.

  1. <div id="foo">
  2. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  3. </div>
  4. <button onclick="test();">test</button>
  5. <button onclick="alert(2);">test2</button>
  6. <script type="text/javascript">
  7. function long_run(sep) {
  8. console.log('start');
  9. var s = +new Date();
  10. while (+new Date() - s < sep) {
  11. //
  12. }
  13. console.log('end');
  14. }
  15. function test() {
  16. var el = document.getElementById("foo");
  17. el.style.backgroundColor = "blue";
  18. // do some "long running" task, like sorting data
  19. long_run(3000);
  20. el.style.backgroundColor = "red";
  21. }

在js代码执行的时候,UI更新被冻结:
1. 表现为UI失去响应,例如点击没反应;
2. handleClick函数里对dom的修改不会立即更新;
3. 点击等其他事件触发的handle不会立即执行,被放入队列中依次执行,但是事件对应的UI update 被抛弃。

例子2.

  1. <div id="foo">
  2. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  3. </div>
  4. <button onclick="test();">test</button>
  5. <button onclick="alert(2);">test2</button>
  6. <script type="text/javascript">
  7. function long_run(sep) {
  8. console.log('start');
  9. var s = +new Date();
  10. while (+new Date() - s < sep) {}
  11. console.log('end');
  12. }
  13. function test() {
  14. console.log('setTimeout');
  15. var el = document.getElementById("foo");
  16. el.style.backgroundColor = "blue";
  17. setTimeout(function() {
  18. el.style.backgroundColor = "red";
  19. }, 50);
  20. long_run(3000);
  21. }
  22. </script>

Figure 1 - JavaScript UI Queue and UI Thread lanes depicted: timed code is intercalated taking turns
Figure 1 - JavaScript UI Queue and UI Thread

pic from

Script run time Limits of every browser

“0.1 second [100ms] is about the limit for having the user feel that the system is reacting instantaneously, meaning that no special feedback is necessary except to display the result.”
- Jakob Nielsen

推荐每个js片段执行最长时间限制为50ms。

定时器

因为浏览器的单线程机制,所以定时器真正执行的时间是不能保证的。
即setTimeout(fn, 100) 或 setInterval(fn, 100),是不能确保在调用完之后100ms就会执行fn,具体的执行时间取决于于当前任务队列中的任务和等待把fn放入任务队列期间是否有其他立即执行的事件加入到队列中。
timer.png
例子3.

  1. <div id="foo">
  2. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  3. </div>
  4. <button onclick="handleClick();">test</button>
  5. <button onclick="alert(2);">test2</button>
  6. <script type="text/javascript">
  7. function long_run(sep) {
  8. console.log('start');
  9. var s = +new Date();
  10. while (+new Date() - s < sep) {
  11. //
  12. }
  13. console.log('end');
  14. }
  15. function handleClick() {
  16. console.log('handle click');
  17. long_run(11);
  18. //执行了11ms,从18ms到29ms结束
  19. }
  20. function fun1(){
  21. console.log('setTimeout called');
  22. long_run(7);//执行了7ms,从29ms到36ms结束
  23. }
  24. function fun2(){
  25. console.log('setInterval call');
  26. long_run(5);//执行了5ms
  27. }
  28. //t = 0ms,启动一个脚本,执行18ms,此时队列为空
  29. long_run(18);//执行18ms
  30. //t=2ms,启动一个setTimeout定时器,,此时队列为空
  31. setTimeout(fun1,10);
  32. //t=8ms,发生了鼠标点击事件,此时队列为空,handleClick立即加入队列
  33. //t=10ms,启动一个10ms setInterval定时器,此时队列有handleClick待执行
  34. setInterval(fun2,10);
  35. //t=12ms,Timer fires,Timer加入队列,此时队列有handleClick和Timer待执行
  36. //t=18ms,之前的script执行完成,此时队列中有handleClick和Timer
  37. //从队列中拿出handleClick进行执行,执行时间为11s,18-29ms,此时队列中有Timer
  38. //t=20ms,10ms interval fires,10ms interval 加入队列,此时队列中有Timer和10ms interval
  39. //t=29ms,handleClick 执行完成,队列中有Timer和10ms interval,拿出Timer进行执行,队列中有10ms interval。
  40. //此时距启动Timer已经过去了27ms(29-2),延时了17ms(27-10)才执行了一个timeout为10ms的程序
  41. //t=30ms,10ms interval fires again,但是此时队列已经存在同一个interval还没执行,所以本次interval 被dropped,队列仍旧中只有10ms interval
  42. // t=36ms,Timer执行完成,从队列中拿出10ms inerval执行,此时队列为空,此时距interval启动已经过去了26ms(36-10)延时了16ms(26-10)才执行了一个10ms的interval。
  43. // t=40ms,10ms interval fires again,队列为空,立马把这个10ms interval放入队列
  44. //t=41ms,10ms interval 执行完,队列中有一个10ms interval,从队列中拿出interval执行,此时距上一个interval执行完成间隔为0,此后队列为空
  45. //t=46ms,10ms interval 执行完,队列为空,
  46. //t=50ms,10ms interval fires,队列为空,马上加入队列,因为线程空闲,立即执行,距离上一个interval执行完成间隔4ms。
  47. </script>

Timer总结

  1. setTimeout(fn,delay) 和setInterval(fn2,delay) fn的执行时间间隔不一定是delay,可能比delay大;而setInterval的fn2执行的间隔则也不一定是delay,可能比delay大,可能比delay小,可能被drop掉。
  2. setInterval触发的时候,如果队列中有同一个interval触发的程序未执行会被drop掉,setTimeout不会有这个问题。

RAF(requestAnimationFrame)ie>=10,chrome>=27,ios7.1,Android4.4

browser support

  1. function test() {
  2. console.log('requestAnimationFrame');
  3. requestAnimationFrame(function() {
  4. var el = document.getElementById("foo");
  5. el.style.backgroundColor = "blue";
  6. requestAnimationFrame(function() {
  7. long_run(3000);
  8. el.style.backgroundColor = "red";
  9. });
  10. });
  11. }
  1. //兼容性处理requestAnimationFrame,对不支持的浏览器用setTimeout来替换,时间间隔为一帧的时间
  2. window.requestAnimFrame = (function() {
  3. return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
  4. window.setTimeout(callback, 1000 / 60);
  5. };
  6. })();

利用 定时器/RAF 分片执行来解决需要长时间执行的js的代码

timed-array-processing-in-javascript

Worker

高级浏览器支持web worker,每一个web worker新起一个线程,通过加载的一个js文件来初始化,不受单线程执行的限制,但是,有很多限制:
1. 不能访问DOM,自然不能修改dom,
2. 不能访问 any global variables or JavaScript functions within the main page
3. access to some objects, including the window, document, and parent, is restricted.
web work.png
pic from

他们通过postMessage/onmessage 来进行发送和接收消息进行交互。
example

CSS3 animations use transform

对于动画效果我们推荐是css3,对于不支持的浏览器才降级使用js来实现。
虽然用css3来实现动画,但是对于浏览器的单线程机制,当js执行的时候,浏览器的UI被冻结,不能update,自然css的动画也被冻结了。好消息是有的浏览器moves the CSS animations off of the UI thread,这样js执行的时候,不会影响css动画的执行。 from
但是有一点这个动画必须animations use transform

支持的浏览器

All Safaris and Andriod Chrome
IE10,Chrome

例子

  1. <head>
  2. <style>
  3. /*旋转的动画*/
  4. .spin {
  5. -webkit-animation: 3s rotate linear infinite;
  6. animation: 3s rotate linear infinite;
  7. background: red;
  8. }
  9. @keyframes rotate {
  10. from {transform: rotate(0deg);}
  11. to {transform: rotate(360deg);}
  12. }
  13. @-webkit-keyframes rotate {
  14. from {-webkit-transform: rotate(0deg);}
  15. to {-webkit-transform: rotate(360deg);}
  16. }
  17. .walkabout-old-school {
  18. -webkit-animation: 3s slide-margin linear infinite;
  19. animation: 3s slide-margin linear infinite;
  20. background: blue;
  21. }
  22. /*位置移动的动画,注意这里是通过transform修改位置*/
  23. @keyframes slide-transform {
  24. from {transform: translatex(0);}
  25. 50% {transform: translatex(300px);}
  26. to {transform: translatex(0);}
  27. }
  28. @-webkit-keyframes slide-transform {
  29. from {-webkit-transform: translatex(0);}
  30. 50% {-webkit-transform: translatex(300px);}
  31. to {-webkit-transform: translatex(0);}
  32. }
  33. .walkabout-new-school {
  34. -webkit-animation: 3s slide-transform linear infinite;
  35. animation: 3s slide-transform linear infinite;
  36. background: green;
  37. }
  38. /*位置移动的动画,通过margin-left来修改位置,这种方法仍然受单线程的影响*/
  39. @keyframes slide-margin {
  40. from {margin-left: 0;}
  41. 50% {margin-left: 100%;}
  42. to {margin-left: 0;}
  43. }
  44. @-webkit-keyframes slide-margin {
  45. from {margin-left: 0;}
  46. 50% {margin-left: 100%;}
  47. to {margin-left: 0;}
  48. }
  49. div {
  50. width: 30px;
  51. height: 30px;
  52. }
  53. </style>
  54. <script>
  55. function kill() {
  56. var start = +new Date;
  57. console.log("" + new Date());
  58. while (+new Date - start < 2000){}
  59. console.log("" + new Date());
  60. }
  61. </script>
  62. </head>
  63. <body>
  64. <p><button onclick="kill()">kill 'em all for 2 sec</button></p>
  65. <p><div class="spin"></div>
  66. <p><div class="walkabout-old-school"></div>
  67. <p><div class="walkabout-new-school"></div>
  68. </body>

how to solve browser single-thread limit

  1. setTimeout/setInterval
  2. RAF
  3. web worker
  4. css3 animations use transform

further read

  1. how-javascript-timers-work
  2. timed-array-processing-in-javascript
  3. Introduction to HTML5 Web Workers: The JavaScript Multi-threadingApproach
  4. web worker
  5. RAF optimizing-visual-updates
  6. high-performance-javascript
  7. css-animations-off-the-ui-thread
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注