setTimeout 问题异步调用

社区平台上有个功能:btn按钮 hover 时执行 出现一个框,框里有Ajax 数据刷新,但获取数据后hover执行另一个function --关闭了,js中更改了dom节点的属性,而且更改成功,但浏览器中竟然看不到更改结果。查找结果如下:

浏览器单进程渲染过程中,将相邻的dom操作做为异步事件,这样dom操作就会被跳过,等到合适时机再执行dom操作,这时执行的dom操作已经和当初的逻辑不在一个浏览器单进程时间片中,即不属于一次堆栈调用。如果将每个dom操作都作为异步事件,那么所有的dom操作都将各自作为一次单独的堆栈调用,这样的话浏览器则会对这些独立的分片后插入一次渲染操作,这样每次dom操作后都渲染到页面中,都能被看到了。而setTimeout则可以实现将一个函数作为一次异步调用放到一个独立的堆栈中,尽管setTimeout的delay是0,也会作为一次异步调用,而每次异步调用结束后都会render页面,因此就比浏览器批量操作dom后一次render的体验更佳。

加了setTimeout就OK 了 

blog: http://blog.csdn.net/kongls08/article/details/6996528:

首先明确一点,多数js引擎是单进程的解释器,或者说在一个web页面中,js是单进程执行的,所谓单进程,就是 浏览器无法在渲染页面的同时执行 js,这里说的渲染是将粒度放大的一个“渲染”操作,不论浏览器渲染页面有多快,总会耗费一定的时间,在这个时间端内浏览器干不了其他的事情,就类似在 cpu的最小时间片单位中,cpu也只能针对一个任务进行运算。虽然浏览器调度渲染和js线程的时间片长度远大于cpu的最小时间片。此外,浏览器是顺序调用堆栈中的函数,比如图:
setTimeout1
js引擎过滤js代码的时候,将代码段进行拆分,在js修改dom节点之后进跟着会render一下页面,好让页面看到js操作dom后的结果,这是合情合理的,当然,通常情况下我们希望浏览器是按照这样固定的逻辑执行,而且大部分浏览器在多数情况下也是这么做的,然而有时候会有偏差。比如, 当js中的若干个改变dom节点的操作相互紧临,而且每两个操作dom节点的操作共花费的时间小于浏览器处理单进程的最小时间片,这时不同浏览器的表现就出现不一致,但通常在浏览器内存比较充裕的情况下,浏览器会将这若干个连续的dom操作会按照浏览器最小分片时间进行分割,即可能两三个dom操作的时间和大于浏览器处理单进程的最小时间片,这是两三个dom操作后浏览器才渲染一次页面,但在 浏览器内存吃紧的情况下,有些浏览器会将这些相互时间间隔小于浏览器的单进程时间片的dom操作集合,合并到一次浏览器操作,并作为一次堆栈调度,这样的话,浏览器会等待这些紧邻的dom操作结束后一次渲染页面,如图:
setTimeout2
不同浏览器对单进程最小分片都有不同的尺寸定义,而且不同浏览器也处于对js引擎速度的考虑,会合并若干次dom操作到一次堆栈调度中,多数浏览器对这中js引擎的hack作的很缜密,尽管会有因为渲染不及时带来的用户体验不佳,但还至少能做到慢吞吞的一边烧着cpu一边render页面,但ie自作聪明的多作了一些存在严重bug的hack,比如ie中认为没有hasLayout属性的dom节点的js操作不会触发render,再比如有时dom没有display=block的属性则改变其属性不会触发rander,因此很多人在写js的时候,常会遇到一些很奇怪的事情,抱怨明明在 js中更改了dom节点的属性,而且更改成功,但浏览器中竟然看不到更改结果。为了改善这种状况,只有一个方法:异步调用
我们通常理解的异步概念大都来自于ajax,即页面向后端发起请求,这时不应当等待返回结果,而是继续执行,等有返回的时候就执行回调,这里的回调函数执行的时机是不固定的,准确说是依赖于后端的返回。在 浏览器单进程渲染过程中,将相邻的dom操作做为异步事件,这样dom操作就会被跳过,等到合适时机再执行dom操作,这时执行的dom操作已经和当初的逻辑不在一个浏览器单进程时间片中,即不属于一次堆栈调用。如果将每个dom操作都作为异步事件,那么所有的dom操作都将各自作为一次单独的堆栈调用,这样的话浏览器则会对这些独立的分片后插入一次渲染操作,这样每次dom操作后都渲染到页面中,都能被看到了。而 setTimeout则可以实现将一个函数作为一次异步调用放到一个独立的堆栈中,尽管setTimeout的delay是0,也会作为一次异步调用,而每次异步调用结束后都会render页面,因此就比浏览器批量操作dom后一次render的体验更佳

[html]  view plain copy
  1. <input type="text" value="a" name="input" οnkeydοwn="alert(this.value)" />
  2. <input type="text" value="a" name="input"
  3. οnkeydοwn="var me=this;setTimeout(function(){alert(me.value)},0)" />
我们试一下在文本域的a后添加新的字符串。
其中第二个例子说明了setTimeout和浏览器事件的异步调用。虽然setTimeout的delay是0,但仍然被放到了一个另外的堆栈调用中, 在事件结束后才调用
setTimeout2
明白了这个过程,也就明白了为什么使用 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就够了。
,JavaScript引擎一直等待着任务队列中任务的到来.由于单线 程关系,这些任务得进行排队,一个接着一个被引擎处理.

在JavaScript引擎运行脚本期间,浏览器渲染线程都是处于挂起状态的,也就是说被”冻结”了.
所以,在脚本中执行对界面进行更新操作,如添加结点,删除结点或改变结点的外观等更新并不会立即体现出来,这些操作将保存在一个队列中,待JavaScript引擎空闲时才有机会渲染出来.

如果队列非空,引擎就从队列头取出一个任务,直到该任务处理完,即返回后引擎接着运行下一个任务,在任务没返回前队列中的其它任务是没法被执行的.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值