早起的菜鸟

FEer---思考,感悟,总结,翻译,分享

导航

Javascript setTimeout

Overview:

首先弄清setTimeout工作原理,必须先确定几个概念:

  • Javascript运行时(引擎)是单线程的。
  • 浏览器内核:常驻线程包括
    • GUI线程 (主要负责页面呈现,reflowrepaint
    • js引擎线程
    • 事件线程  
    • 定时器线程
    • http请求等

  GUI渲染线程负责渲染网页,GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起。页面将停止一切解析和渲染的行为 --- 浅析浏览器解析和渲染

  这些线程,都会产生不同的异步事件。

针对“单线程”,那么,异步生成的事件都会添加到队列的末尾。

那么setTimeout(function(){...}0),这里的0s delay 可以理解为,什么时候执行回调,这里是执行到setTimeout创建个timer,并0s后添加到执行队列(尾)。

注意,这里并不一定就执行了。执行与否,取决于当前调用堆栈是否为空。即Task队列在当前时刻只有timer任务,那么此刻就执行Timer callback function。

Details:

想像一个场景:

"do sth." button 和 一个 显示结果的 result Div,button绑定一个onclick handler:

  1. 一段运行很久的计算程序(比如,3min)
  2. div 显示计算结果

然后你点击button,等了3min,3min内看不到任何变化。再点击button,等了1min,你还是看不到任何变化。你又点击……

 

添加一个显示状态的status DIV,修改onclick handler(function longCal):

  1. div 显示“caculating” (将会持续3min)
  2. 执行longCal(运行3min)
  3. 在result div显示结果
  4. 在status Div 显示状态“done”

可是,你看不到status Div 出现caculating。

那么把任务分解,每一步都做了什么。

注:浏览器会把所有event生成的TODO 任务 放在一个单队列。而且,重排status Div,显示“caculating” 需要新开一个单独的任务,并放到当前调用队列末尾。

这里的event可以理解为一个个任务,基于任务驱动js引擎执行。js引擎,单线程,源源不断接受新任务(若有),并插入执行队列尾。

  • queue:空队列
  • event:点击button,把1~4行 添加到队列中(尾部)
  • event:执行这里说“执行”不是很准确,在这里根本没有真正执行,只是由browser engine将当前执行的代码块创建的任务并放到插入执行队列尾,下同)第1行(显示“caculating”)Task,后续任务:执行第2~4行;重排status Div(显示caculating)
    注:这里执行到第一行,需要更新DOM,需要由DOM change event 触发另一个线程,因此排到队列末尾
  • event:执行2nd行(caculation),后续任务:执行第3~4行;status Div 显示caculating
  • event:执行3rd 行(result Div 显示caculated result),后续任务:执行4th行;status Div 显示caculating;result Div 显示result
  • event:执行4th行(status Div 显示done),后续任务:status Div 显示caculating;result Div 显示result;status Div 显示done
  • event:这时,onclick handle 执行结束,等待下一个task。onclick handle 移出队列,后续任务:status Div 显示caculating;result Div 显示result;status Div 显示done
  • event:status Div 显示caculating
  • event:result Div 显示result
  • event:status Div 显示done

    由于执行很快,由caculation→done, 几乎一闪而过。所以感觉根本没有caculating显示。

你可以看出:你预期是想要在执行caculation过程中,status Div 显示caculating的,可以事实却根本没有显示。

How to solve:

使用setTimeout(),0s delay,然后

修改onclick handle:

  1. div 显示“caculating” (将会持续3min)
  2. 执行setTimeout,0s timeout,回调函数调用“longCal”:longCal函数同上,只不过没有第一行。

任务分解:

  1. queue:空
  2. event:click button,后续任务:更新status Div;执行setTimeout
  3. event:执行1st行(更新status Div 显示caculating),后续任务:setTimeout;更新status div显示caculating
  4. event:调用setTimeout,后续任务:更新status div显示caculating 注:假设0s内没有任何其他定时任务
  5. event:0s后 ,此时任务队列:更新status div显示caculating;执行longCal 函数
  6. event:更新status div显示caculating,后续任务:执行longCal
    注:这里更新 status div显示caculating 会在定时器0s触发回调前执行。创建定时器后,继续执行下一个任务,计时任务交给定时器线程定时。满足timeout后,添加到js引擎执行队列尾,等待执行。
    这里的浏览器模型定时计数器并不是由JavaScript引擎计数的,因为JavaScript引擎是单线程的,如果处于阻塞线程状态就计不了时,它必须依赖外部来计时并触发定时,所以队列中的定时事件也是异步事件. ---  深入理解JavaScript定时机制---定时触发线程
  7. 同上

可以看出:你能看到预期的caculating。

References:

1. setTimeout with zero delay used often in web pages, why?

2. 深入理解JavaScript定时机制

3. How JavaScript Timers Work

4. Why does setTimeout(fn, 0) sometimes help?

5. HOW BROWSERS WORK: BEHIND THE SCENES OF MODERN WEB BROWSERS

posted on 2012-03-27 12:42  早起的菜鸟  阅读(1102)  评论(0编辑  收藏  举报