您的位置:首页 > Web前端 > JavaScript

setTimeout与js引擎的异步执行

2010-05-07 11:59 218 查看
Friday, August 21, 2009, 06:43 PM
Posted by Administrator
从岁月如歌那里看到一篇文章,是说“大数组的分时优化处理”,讲述了如何使用timedChunk来改善用户体验,所谓timedChunk的确可以很大程度改善用户体验,但文章并无介绍这种优化性能方法的深层原因,而且“大数组“的例子会让很多人产生误解,setTimeout的用处不止如此。这里的timedChunk是Nicholas C. Zakas对js引擎单进程使用setTimeout进行hack的一种叫法。John Resig很早就给出了setTimeout工作机制的一种解释,这个解释基本全面的描述了单进程模式的js引擎对setTimeout的处理,并无对所有浏览器的js引擎作详细分析,毕竟并不是所有的js引擎都是单进程。不过这篇文章已经相当权威了。

timedChunk是如何根据setTimeout来优化体验呢?为何setTimeout只能部分优化体验,而不能优化性能?什么时候需要使用 setTimeout?如何使用setTimeout对ie作hack?为什么ie的js引擎要对ECMAScript模式作hack?本文将对这些问题一一解答。

首先明确一点,多数js引擎是单进程的解释器,或者说在一个web页面中,js是单进程执行的,所谓单进程,就是浏览器无法在渲染页面的同时执行 js,这里说的渲染是将粒度放大的一个“渲染”操作,不论浏览器渲染页面有多快,总会耗费一定的时间,在这个时间端内浏览器干不了其他的事情,就类似在 cpu的最小时间片单位中,cpu也只能针对一个任务进行运算。虽然浏览器调度渲染和js线程的时间片长度远大于cpu的最小时间片。此外,浏览器是顺序调用堆栈中的函数,比如图:



图中可以看到,js引擎过滤js代码的时候,将代码段进行拆分,在js修改dom节点之后进跟着会render一下页面,好让页面看到js操作 dom后的结果,这是合情合理的,当然,通常情况下我们希望浏览器是按照这样固定的逻辑执行,而且大部分浏览器在多数情况下也是这么做的,然而有时候会有偏差。比如,当js中的若干个改变dom节点的操作相互紧临,而且每两个操作dom节点的操作共花费的时间小于浏览器处理单进程的最小时间片,这时不同浏览器的表现就出现不一致,但通常在浏览器内存比较充裕的情况下,浏览器会将这若干个连续的dom操作会按照浏览器最小分片时间进行分割,即可能两三个 dom操作的时间和大于浏览器处理单进程的最小时间片,这是两三个dom操作后浏览器才渲染一次页面,但在浏览器内存吃紧的情况下,有些浏览器会将这些相互时间间隔小于浏览器的单进程时间片的dom操作集合,合并到一次浏览器操作,并作为一次堆栈调度,这样的话,浏览器会等待这些紧邻的dom操作结束后一次渲染页面,如图:



当然,不同浏览器对单进程最小分片都有不同的尺寸定义,而且不同浏览器也处于对js引擎速度的考虑,会合并若干次dom操作到一次堆栈调度中,多数浏览器对这中js引擎的hack作的很缜密,尽管会有因为渲染不及时带来的用户体验不佳,但还至少能做到慢吞吞的一边烧着cpu一边render页面,但 ie自作聪明的多作了一些存在严重bug的hack,比如ie中认为没有hasLayout属性的dom节点的js操作不会触发render,再比如有时 dom没有display=block的属性则改变其属性不会触发rander,因此很多wd在写js的时候,常会遇到一些很奇怪的事情,抱怨明明在js 中更改了dom节点的属性,而且更改成功,但浏览器中竟看不到更改结果,然后就一顿抓狂。

其实岁月如歌用循环数组的方式给的例子也很好的说明了这种浏览器吃力的堆栈调度带来的体验不佳,如果再把range设的大一点,ie甚至会死掉。然而有一种方法可以缓解浏览器对相邻dom操作的吃力状况:异步调用。

我们通常理解的异步概念大都来自于ajax,即页面向后端发起请求,这时不应当等待返回结果,而是继续执行,等有返回的时候就执行回调,这里的回调函数执行的时机是不固定的,准确说是依赖于后端的返回。在浏览器单进程渲染过程中,将相邻的dom操作做为异步事件,这样dom操作就会被跳过,等到合适时机再执行dom操作,这时执行的dom操作已经和当初的逻辑不在一个浏览器单进程时间片中,即不属于一次堆栈调用。如果将每个dom操作都作为异步事件,那么所有的dom操作都将各自作为一次单独的堆栈调用,这样的话浏览器则会对这些独立的分片后插入一次渲染操作,这样每次dom操作后都渲染到页面中,都能被看到了。而setTimeout则可以实现将一个函数作为一次异步调用放到一个独立的堆栈中,尽管setTimeout的delay是0,也会作为一次异步调用,而每次异步调用结束后都会render页面,因此就比浏览器批量操作dom后一次render的体验更佳,看这个例子就明白了。

例子:http://www.uedmagazine.com/test/setTimeout.html

其中第二个例子说明了setTimeout和浏览器事件的异步调用。虽然setTimeout的delay是0,但仍然被放到了一个另外的堆栈调用中,在事件结束后才调用。



明白了这个过程,也就明白了为什么使用setTimeout只会改善体验而不会改善性能,setTimeout会多出许多render操作,当然会慢,我给的例子中很明显可以看出,异步渲染页面的时间多耗费了将近一倍,但和用户体验的提升相比,还是值得的。因此,当js逻辑中有大量的循环造成连续的修改dom节点,这时就应当使用setTimeout来改善体验。如果在调试ie的时候发现没有及时render页面,可以使用setTimeout来 hack,原因如上。ie对连续dom操作的hack大概是处于性能考虑,windows的性能本来就不敢恭维,再跑ie就更慢,各种网测也足以证明ie 的js引擎是最糟糕的,因此ie要搞一些css expression和haslayout这些怪胎出来作性能hack就不足为怪了。回头看岁月入歌的分析,那个所谓的25ms是对浏览器单进程调度最小时间片的一种猜测值,这个25ms应当和连续dom操作的个数有关,所以这个猜测值意义并不大。如果将每个dom操作都setTimeout包住,delay设为0就够了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: