setTimeout(func, 0)可以使用在很多地方,拆分循环、模拟事件捕获、页面渲染等
一、setTimeout中的delay参数为0,并不是指马上执行
<script type="text/javascript">
function delay1() {
console.log('delay1');
}
function delay2() {
console.log('delay2');
}
function delay3() {
console.log('delay3');
}
delay1();
setTimeout(function() {
delay2();
}, 0);
delay3();
</script>
用firefox的firebug可以查看到,并不是按照delay1,delay2,delay3这样打印的。
由于JavaScript是单线程处理任务的,而setTimeout是异步事件,延时时间为0的时候,JavaScript引擎会把异步事件立刻放到任务队列里,而不是立刻执行,需要等到前面处于等待状态的事件处理程序全部执行完成后再调用它(JavaScript engines only have a single thread, forcing asynchronous events to queue waiting for execution)。JavaScript引擎的运作可以查看前面的文章《Javascript定时器(一)——单线程》。
二、拆解循环
循环体中包含太多的操作和循环的次数过多都会导致循环执行时间过长,并直接导致锁死浏览器。如果循环之后没有其他操作,每次循环只处理一个数值,而且不依赖于上一次循环的结果则可以对循环进行拆解。
<script type="text/javascript">
function delay() {
for(var i=0; i<100000000; i++) {
//logic
}
}
delay();
</script>
使用setTimeout拆解循环,用firebug的console.log打印index值。
<script type="text/javascript">
var index = 0;
//使用setTimeout拆解循环
function major() {
if(index > 100000000) {
return;
}
console.log(index);
index++;
setTimeout(function() {
major();
}, 0);
}
major();
</script>
三、模拟事件捕获
浏览器的 DOM 事件都是采用冒泡的方式,在实际的开发过程中可能存在需要事件捕获的需求,要求子元素的事件在父元素触发之后才能触发。
<p>1、父元素的事件在子元素触发之后触发</p>
<div id="parent">
<a href="javascript:void(0)" id="child">冒泡</a>
<p id="result1"></p>
</div>
1、一个a按钮
2、一个p显示结果
3、一个div作为父级元素
<script type="text/javascript">
//父元素的事件在子元素触发之后触发
function bubbling() {
var child = document.getElementById('child'),
parent = document.getElementById('parent'),
result = document.getElementById('result1');
child.onclick = function() {
result.innerHTML += 'child->';
};
parent.onclick = function() {
result.innerHTML += 'parent->';
};
}
</script>
1、绑定a按钮的click事件,点击一下打印child
2、绑定div按钮的click事件,点击一下打印parent
3、显示结果如下:
在a按钮的click事件中加个setTimeout,就能做到子元素的事件在父元素触发之后触发了,这里也是JavaScript引擎在起作用。
child.onclick = function() {
setTimeout(function() {
result.innerHTML += 'child->';
}, 0);
};
四、页面渲染
浏览器中GUI渲染线程与JavaScript引擎是互斥的,所以当JavaScript执行时,浏览器就不会做任何的页面渲染。
<p>3、页面渲染,阻塞</p>
<a href="javascript:void(0)" id="vary">变换</a>
<div style="width:150px;height:150px;border:1px solid #c6c6c6" id="container"></div>
1、点击变换按钮,将下面container的宽与高从150减到1。
2、由于互斥的关系,效果感觉是一下子从150到1了,中间没有那个减小的过程。
<script type="text/javascript">
//页面渲染
function render() {
var btn = document.getElementById('vary'), container = document.getElementById('container');
btn.onclick = function() {
for (var i = 150; i > 0; i--) {
container.style.height = i + 'px';
container.style.width = i + 'px';
}
};
}
</script>
1、修改container的height与width会引发回流(reflow),一回流GUI渲染线程线程就会执行。
2、由于互斥的原因,只有当JavaScript执行完毕后,才会做渲染。
=》
用setTimeout后,就会出现从150到1的减小过程。
function vary(i) {
i--;
console.log(i);
container.style.height = i + 'px';
container.style.width = i + 'px';
if(i > 0) {
setTimeout(function() {
vary(i);
}, 0);
}
}
btn.onclick = function() {
var i = 150;
vary(i);
};
Tips:
这里顺便说下重绘(Repaint)与回流(reflow):
重绘(repaints):是一个元素外观的改变所触发的浏览器行为,例如改变vidibility、outline、背景色等属性。浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。重绘不会带来重新布局,并不一定伴随回流。
回流(reflow):是更明显的一种改变,可以理解为渲染树需要重新计算。
导致 reflow 发生的一些因素:
- 调整窗口大小(Resizing the window)
- 改变字体(Changing the font)
- 增加或者移除样式表(Adding or removing a stylesheet)
- 内容变化,比如用户在input框中输入文字(Content changes, such as a user typing text in an input box)
- 激活 CSS 伪类,比如 :hover (IE 中为兄弟结点伪类的激活)(Activation of CSS pseudo classes such as :hover (in IE the activation of the pseudo class of a sibling))
- 操作 class 属性(Manipulating the class attribute)
- 脚本操作 DOM(A script manipulating the DOM)
- 计算 offsetWidth 和 offsetHeight 属性(Calculating offsetWidth and offsetHeight)
- 设置 style 属性的值 (Setting a property of the style attribute)
demo下载:
http://download.csdn.net/download/loneleaf1/7971185
参考资料:
http://heroicyang.com/2012/09/22/javascript-timer-in-depth/ 深入理解JavaScript定时器
http://heroicyang.com/2012/10/14/javascript-timer-and-event-in-depth/ 深入理解JavaScript定时器(续)
http://my.oschina.net/cupidooo/blog/149379 Web前端:解决浏览器页面回流(reflow)的几种方法
http://www.planabc.net/2009/04/13/reflow/ 影响 reflow 的因素及其优化
http://www.nowamagic.net/librarys/veda/detail/787 优化js脚本设计,防止浏览器假死