@fengfeng
2015-04-27T07:50:25.000000Z
字数 6866
阅读 2585
js
浏览器
single-thread
Timer
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.
<div id="foo">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</div>
<button onclick="test();">test</button>
<button onclick="alert(2);">test2</button>
<script type="text/javascript">
function long_run(sep) {
console.log('start');
var s = +new Date();
while (+new Date() - s < sep) {
//
}
console.log('end');
}
function test() {
var el = document.getElementById("foo");
el.style.backgroundColor = "blue";
// do some "long running" task, like sorting data
long_run(3000);
el.style.backgroundColor = "red";
}
在js代码执行的时候,UI更新被冻结:
1. 表现为UI失去响应,例如点击没反应;
2. handleClick函数里对dom的修改不会立即更新;
3. 点击等其他事件触发的handle不会立即执行,被放入队列中依次执行,但是事件对应的UI update 被抛弃。
例子2.
<div id="foo">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</div>
<button onclick="test();">test</button>
<button onclick="alert(2);">test2</button>
<script type="text/javascript">
function long_run(sep) {
console.log('start');
var s = +new Date();
while (+new Date() - s < sep) {}
console.log('end');
}
function test() {
console.log('setTimeout');
var el = document.getElementById("foo");
el.style.backgroundColor = "blue";
setTimeout(function() {
el.style.backgroundColor = "red";
}, 50);
long_run(3000);
}
</script>
Figure 1 - JavaScript UI Queue and UI Thread
“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放入任务队列期间是否有其他立即执行的事件加入到队列中。
例子3.
<div id="foo">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</div>
<button onclick="handleClick();">test</button>
<button onclick="alert(2);">test2</button>
<script type="text/javascript">
function long_run(sep) {
console.log('start');
var s = +new Date();
while (+new Date() - s < sep) {
//
}
console.log('end');
}
function handleClick() {
console.log('handle click');
long_run(11);
//执行了11ms,从18ms到29ms结束
}
function fun1(){
console.log('setTimeout called');
long_run(7);//执行了7ms,从29ms到36ms结束
}
function fun2(){
console.log('setInterval call');
long_run(5);//执行了5ms
}
//t = 0ms,启动一个脚本,执行18ms,此时队列为空
long_run(18);//执行18ms
//t=2ms,启动一个setTimeout定时器,,此时队列为空
setTimeout(fun1,10);
//t=8ms,发生了鼠标点击事件,此时队列为空,handleClick立即加入队列
//t=10ms,启动一个10ms setInterval定时器,此时队列有handleClick待执行
setInterval(fun2,10);
//t=12ms,Timer fires,Timer加入队列,此时队列有handleClick和Timer待执行
//t=18ms,之前的script执行完成,此时队列中有handleClick和Timer
//从队列中拿出handleClick进行执行,执行时间为11s,18-29ms,此时队列中有Timer
//t=20ms,10ms interval fires,10ms interval 加入队列,此时队列中有Timer和10ms interval
//t=29ms,handleClick 执行完成,队列中有Timer和10ms interval,拿出Timer进行执行,队列中有10ms interval。
//此时距启动Timer已经过去了27ms(29-2),延时了17ms(27-10)才执行了一个timeout为10ms的程序
//t=30ms,10ms interval fires again,但是此时队列已经存在同一个interval还没执行,所以本次interval 被dropped,队列仍旧中只有10ms interval
// t=36ms,Timer执行完成,从队列中拿出10ms inerval执行,此时队列为空,此时距interval启动已经过去了26ms(36-10)延时了16ms(26-10)才执行了一个10ms的interval。
// t=40ms,10ms interval fires again,队列为空,立马把这个10ms interval放入队列
//t=41ms,10ms interval 执行完,队列中有一个10ms interval,从队列中拿出interval执行,此时距上一个interval执行完成间隔为0,此后队列为空
//t=46ms,10ms interval 执行完,队列为空,
//t=50ms,10ms interval fires,队列为空,马上加入队列,因为线程空闲,立即执行,距离上一个interval执行完成间隔4ms。
</script>
function test() {
console.log('requestAnimationFrame');
requestAnimationFrame(function() {
var el = document.getElementById("foo");
el.style.backgroundColor = "blue";
requestAnimationFrame(function() {
long_run(3000);
el.style.backgroundColor = "red";
});
});
}
//兼容性处理requestAnimationFrame,对不支持的浏览器用setTimeout来替换,时间间隔为一帧的时间
window.requestAnimFrame = (function() {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
timed-array-processing-in-javascript
高级浏览器支持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.
pic from
他们通过postMessage/onmessage 来进行发送和接收消息进行交互。
example
对于动画效果我们推荐是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
<head>
<style>
/*旋转的动画*/
.spin {
-webkit-animation: 3s rotate linear infinite;
animation: 3s rotate linear infinite;
background: red;
}
@keyframes rotate {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
@-webkit-keyframes rotate {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(360deg);}
}
.walkabout-old-school {
-webkit-animation: 3s slide-margin linear infinite;
animation: 3s slide-margin linear infinite;
background: blue;
}
/*位置移动的动画,注意这里是通过transform修改位置*/
@keyframes slide-transform {
from {transform: translatex(0);}
50% {transform: translatex(300px);}
to {transform: translatex(0);}
}
@-webkit-keyframes slide-transform {
from {-webkit-transform: translatex(0);}
50% {-webkit-transform: translatex(300px);}
to {-webkit-transform: translatex(0);}
}
.walkabout-new-school {
-webkit-animation: 3s slide-transform linear infinite;
animation: 3s slide-transform linear infinite;
background: green;
}
/*位置移动的动画,通过margin-left来修改位置,这种方法仍然受单线程的影响*/
@keyframes slide-margin {
from {margin-left: 0;}
50% {margin-left: 100%;}
to {margin-left: 0;}
}
@-webkit-keyframes slide-margin {
from {margin-left: 0;}
50% {margin-left: 100%;}
to {margin-left: 0;}
}
div {
width: 30px;
height: 30px;
}
</style>
<script>
function kill() {
var start = +new Date;
console.log("" + new Date());
while (+new Date - start < 2000){}
console.log("" + new Date());
}
</script>
</head>
<body>
<p><button onclick="kill()">kill 'em all for 2 sec</button></p>
<p><div class="spin"></div>
<p><div class="walkabout-old-school"></div>
<p><div class="walkabout-new-school"></div>
</body>