苦逼前端

JS中的延迟执行与GUI渲染

Javascript2013-11-21 15:41

最近在做这个编辑器的时候,反复用到延迟执行,在这里总结一下。

先来看一段代码:

alert(1)
setTimeout(function(){
    alert(2);
}, 0);
alert(3); 

看上去应该是依次弹出1,2,3。点击运行后发现,弹出顺序却是1,3,2。为什么会这样呢?不是设置了0秒的延时吗?为啥还是跑到最后执行了。难道是因为这一步比较耗时,所以就先弹出了3?

我们来试另一段代码:

setTimeout(function(){
    alert(2);
}, 0);
new Array(1e8).join();
alert(3);

运行后,我们发现,尽管在弹出3之前加了一步更耗时的操作,2依然要等到3执行了才会执行。这说明,setTimeout中的函数,是放到异步事件队列里面了,当前同步的代码执行完毕,才能开始事件轮询,然后才会开始执行。

我们再来一段代码:

$('body').css('background', 'blue');
setTimeout(function(){
    $('body').css('background', '#f9f9f9');
}, 5000);
new Array(1e8).join();

第5行加了一步同步的耗时操作,我们发现第1行的代码,要等到第5行代码执行完毕才会开始执行,其实这个指令浏览器是瞬时接收到的,但是GUI渲染要等到同步操作执行完毕后才会开始执行。

执行插入节点操作后,绑定事件的函数已经执行,然而GUI却还没有开始渲染。所以就会发生当我们动态创建节点并添加到dom的时候,新创建的元素上绑定的事件不执行的情况。高级浏览器可能有所改善,但IE8以下一定会出现这样的问题。

这说明,浏览器的执行顺序应该是:同步代码>GUI渲染。

那么GUI渲染和异步事件队列,哪个优先级高呢?再看下面一段:

$('body').css('background', 'blue');
setTimeout(function(){
    new Array(1e8).join();
    $('body').css('background', '#f9f9f9');
}, 0);
new Array(1e8).join();
alert('done!'); 

我们发现,alert这货居然和body变色同时发生...也就是说,alert其实也会触发GUI渲染。

既然它可以触发GUI渲染,那么它就一定可以阻塞JS的运行,我们一直不点确定,过很久之后再点,发现果然setTimeout里面的耗时操作还会发生,这说明浏览器的执行顺序: GUI渲染>异步事件队列。

等等,接着看下面:

$('.content').css('background', 'blue');
setTimeout(function(){
    new Array(1e8).join();
    $('.content').css('background', '#f9f9f9');
}, 0);
new Array(1e8).join();
console.log('done'); 

纳尼?这段代码和上面的差别只是把alert换成了console,可是body却不变色了?这又是什么原因呢?

猜想可能是上面的那句alert,强制触发了页面渲染,所以浏览器运行完同步代码的时候,立马就进行了页面渲染。想想这也是合情合理的,因为alert也是浏览器窗口的视图,想要在同步代码执行完毕就立马显示,就必须强制触发页面渲染。。

如果没有alert强制渲染,则要等到马上就要发生的异步事件队列里面的函数执行完毕后才发生渲染。

我实验了下,chrome下这个临界值在1500ms左右。

综上:
在同步代码有alert的情况下,浏览器的执行顺序为:同步代码>GUI渲染>异步事件队列。 同步代码没有alert的情况下,浏览器的执行顺序为:同步代码>(1500ms内的)异步事件队列>GUI渲染>(1500ms外的)异步事件队列

纠错:
上面得出的临界值,应该跟显卡的运算能力有关,显卡越强劲,每秒渲染的帧数越多,那么这个临界值就越小。可以理解为:浏览器在接收到渲染请求后,会告诉显卡处理这个渲染,但是过了一小会浏览器再次告诉显卡要进行另外一个渲染,但是当前这个还没渲染完呢,那就直接把页面渲染成把两个结果结合起来的效果吧!(测试浏览器: chrome29)

评论(1)
  • 61.135.169.*: 非常赞6年5个月前
还可输入200个字