开发过程中,有时发现一个函数debug跟踪一下就执行正常,不跟踪就不执行。然后,加一个setTimeout(fn,0)然后,运行正常了。
今天深入来分析一下这个问题存在的原因。
知识点:
一:js单线程
js执行和主UI线程执行交替进行。 主ui线程又包括paint和redraw(reflow)
二:事件队列(event queue)
dom事件、setTimeout、网络请求事件 都放入事件队列。依次执行。
代码分析:
HTML code:
<table border=1>
<tr><td><button id='do'>Do long calc - bad status!</button></td>
<td><div id='status'>Not Calculating yet.</div></td>
</tr>
<tr><td><button id='do_ok'>Do long calc - good status!</button></td>
<td><div id='status_ok'>Not Calculating yet.</div></td>
</tr>
</table>
JavaScript code: (Executed on onDomReady
and may require jQuery 1.9)
function long_running(status_div) {
var result = 0;
// Use 1000/700/300 limits in Chrome,
// 300/100/100 in IE8,
// 1000/500/200 in FireFox
// I have no idea why identical runtimes fail on diff browsers.
for (var i = 0; i < 1000; i++) {
for (var j = 0; j < 700; j++) {
for (var k = 0; k < 300; k++) {
result = result + i + j + k;
}
}
}
$(status_div).text('计算完成');
}
// Assign events to buttons
$('#do').on('click', function () {
$('#status').text('计算中....');
long_running('#status');
});
$('#do_ok').on('click', function () {
$('#status_ok').text('计算中
....');
// This works on IE8. Works in Chrome
// Does NOT work in FireFox 25 with timeout =0 or =1
// DOES work in FF if you change timeout from 0 to 500
window.setTimeout(function (){ long_running('#status_ok') }, 0);
});
一般认为:先显示 status Div,先显示运算中,然后进行运算,并显示运算完成。
运行结果:计算中没有显示,直接运行一会然后显示运算完成。
问题追踪:$('#status').text('计算中....'); dom操作完成后,又产生了一个dom 重绘事件,这个事件放到了js计算后面,结果被计算完成快速覆盖了。
john resig 对timer 的阐述:http://ejohn.org/blog/how-javascript-timers-work/
这个对js的单线程、事件队列讲的很清楚。对js使用timer做重计算的方法也有说明。
stackovflow 对 setTimeout 0 的解释:http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful/23747597#23747597
这里面有个核心。$('#status').text('计算中') 其实也触发了一个事件。是dom重绘的事件。如果,对dom树、dom的paint,redraw了解的不细致。可能就回导致不容易扑捉的问题。