什么是Ember RunLoop,它是如何工作的?

我想了解Ember RunLoop是如何工作的,以及它是如何工作的。 我已经看过文档 ,但仍然有许多问题。 我有兴趣更好地理解RunLoop是如何工作的,所以我可以在其名称空间中select合适的方法,以便稍后推迟执行某些代码。

  • Ember RunLoop何时启动。 它是依赖于路由器或视图或控制器或其他?
  • 它大概需要多长时间(我知道这是很愚蠢的要求和依赖许多事情,但我正在寻找一个大概的想法,或者如果有一个runloop可能需要的最小或最大时间)
  • RunLoop在任何时候都是被执行的,还是只是表示从开始到执行结束的一段时间,并且可能不会运行一段时间。
  • 如果在一个RunLoop中创build了一个视图,那么在循环结束时,它的所有内容是否会保存到DOM中呢?

原谅我,如果这些是非常基本的问题,我认为这些将帮助像我这样的小菜更好地使用Ember。

2013年10月9日更新:查看运行循环的交互式可视化: https : //machty.s3.amazonaws.com/ember-run-loop-visual/index.html

2013年5月9日更新:下面的所有基本概念仍然是最新的,但是就这个提交而言 ,Ember Run Loop实现已经被拆分成了一个名为backburner.js的独立库,只有一些非常小的API差异。

首先,阅读这些:

http://blog.sproutcore.com/the-run-loop-part-1/

http://blog.sproutcore.com/the-run-loop-part-2/

他们对Ember来说不是100%准确的,但RunLoop背后的核心概念和动机仍然普遍适用于Ember; 只有一些实现细节不同。 但是,在你的问题上:

Ember RunLoop何时启动。 它是依赖于路由器或视图或控制器或其他?

所有的基本用户事件(例如键盘事件,鼠标事件等)都会触发运行循环。 这可以确保捕获的(鼠标/键盘/定时器/等)事件对绑定属性所做的任何更改都会在Ember的数据绑定系统中完全传播,然后再将控制权返还给系统。 所以,移动你的鼠标,按一个键,点击一个button等,都会启动运行循环。

它大概需要多长时间(我知道这是很愚蠢的要求和依赖许多事情,但我正在寻找一个大概的想法,或者如果有一个runloop可能需要的最小或最大时间)

RunLoop永远不会logging它通过系统传播所有更改所花费的时间,然后在达到最大时间限制后停止RunLoop; 相反,RunLoop将一直运行到完成状态,并且不会停止,直到所有已过期的定时器被调用,传播的绑定,或者它们的绑定传播,等等。 显然,单个事件需要传播的变化越多,RunLoop将花费的时间越长。 下面是一个非常不公平的例子,说明RunLoop与没有运行循环的另一个框架(Backbone)相比,如何能够传播更改。http: //jsfiddle.net/jashkenas/CGSd5/ 。 这个故事的寓意是:RunLoop对于你在Ember中要做的大部分事情来说真的很快,而且这也是Ember的力量所在,但是如果你发现自己想要以每秒60帧的速度用JavaScriptanimation30圈,那么在那里可能是比依靠Ember的RunLoop更好的方法去做。

RunLoop在任何时候都是被执行的,还是只是表示从开始到执行结束的一段时间,并且可能不会运行一段时间。

它并不是在任何时候都执行 – 它必须在某个时刻将控制权还给系统,否则你的应用程序就会挂起 – 与有一段while(true)的服务器上的运行循环不同,直到服务器得到一个closures的信号为止… Ember RunLoop没有这样的while(true)但是只响应用户/定时器事件而旋转起来。

如果在一个RunLoop中创build了一个视图,那么在循环结束的时候,是否可以保证它的所有内容都会进入DOM?

让我们看看我们能否弄清楚。 从SC到Ember RunLoop的一个重大变化是,Ember不是在invokeOnceinvokeLast (你在第一个链接关于SproutCore的RL的图中看到)之间来回循环,而是提供了一个“队列”列表,在运行循环的过程中,您可以通过指定动作所属的哪个队列来调度动作(在运行循环期间调用的函数)(例如源代码: Ember.run.scheduleOnce('render', bindView, 'rerender'); )。

如果您查看源代码中的Ember.run.queues = ['sync', 'actions', 'destroy', 'timers']; ,您会看到Ember.run.queues = ['sync', 'actions', 'destroy', 'timers']; ,但是如果你在Ember应用程序的浏览器中打开你的JavaScriptdebugging器并且评估Ember.run.queues ,你会得到一个更完整的队列列表: ["sync", "actions", "render", "afterRender", "destroy", "timers"] 。 Ember保持它们的代码库非常模块化,并且它们使得代码和它自己的代码可以在库的一个单独的部分中插入更多的队列。 在这种情况下,Ember Views库插入renderafterRender队列,特别是在actions队列之后。 我会问为什么可能在一秒钟。 首先,RunLoopalgorithm:

RunLoopalgorithm与上面的SC运行循环文章中描述的几乎相同:

  • 您在RunLoop .begin().end()之间运行代码,只有在Ember中,您需要在Ember.run运行代码,该代码将在内部调用beginend 。 (只有Ember代码库中的内部运行循环代码仍然使用beginend ,所以你应该坚持使用Ember.run
  • end()被调用之后,RunLoop开始传播每一个由传递给Ember.run函数的代码所做的单个更改。 这包括传播绑定属性的值,将视图更改渲染到DOM等等。这些动作(绑定,渲染DOM元素等)的执行Ember.run.queues由上面描述的Ember.run.queues数组决定:
  • 运行循环将从sync的第一个队列开始。 它将运行由Ember.run代码安排到sync队列中的Ember.run操作。 这些操作本身也可以安排在同一个RunLoop中执行更多的操作,并且由RunLoop决定执行每个操作,直到所有的队列被刷新。 这样做的方式是,在每个队列的末尾,RunLoop将查看所有以前刷新的队列,看看是否有任何新的行动已经安排。 如果是这样的话,它必须从最早的队列的开始处开始,执行未执行的预定操作并清除队列,继续追踪其步骤,并在必要时重新开始,直到所有队列完全清空。

这是algorithm的本质。 这就是绑定数据通过应用程序传播的方式。 您可以预期一旦RunLoop运行完成,所有绑定的数据将被完全传播。 那么,DOM元素呢?

队列的顺序,包括由Ember Views库添加的队列,在这里很重要。 注意renderafterRender是在syncactionsync队列包含传播绑定数据的所有操作。 (之后的action在Ember中只是稀疏的使用)。 基于上述algorithm,保证RunLoop到达render队列时,所有的数据绑定都会完成同步。 这是由devise决定的: 同步数据绑定之前,您不希望执行渲染DOM元素的昂贵任务,因为这可能需要使用更新的数据重新渲染DOM元素 – 显然,这是非常低效且错误的,容易清空所有RunLoop队列。 所以Ember能够在呈现render队列中的DOM元素之前,通过它所能完成的所有数据绑定工作进行智能爆发。

所以,最后,要回答你的问题,是的,你可以期望在Ember.run完成的时候,任何必要的DOM渲染都会发生。 这是一个jsFiddle来演示: http : //jsfiddle.net/machty/6p6XJ/328/

关于RunLoop的其他知识

观察员与绑定

重要的是要注意观察者和绑定,虽然具有类似的function来响应“观察”属性的变化,但在RunLoop的上下文中performance完全不同。 如我们所见,绑定传播被调度到sync队列中,最终由RunLoop执行。 另一方面,观察者在被观察的属性改变时立即触发,而不必首先调度到RunLoop队列中。 如果一个观察者和一个绑定全部“观察”同一个属性,那么观察者总是比绑定更新的时间早100%被调用。

scheduleOnceEmber.run.once

Ember自动更新模板的一大提升是基于这样一个事实,即由于RunLoop的原因,多个相同的RunLoop动作可以合并(如果你愿意,可以“去抖”)成一个动作。 如果您查看run_loop.js内部函数,您将看到便于执行此操作的函数是相关的函数scheduleOnceEm.run.once 。 它们之间的区别并不像知道它们存在那么重要,它们如何在队列中丢弃重复的动作以防止在运行循环期间大量的膨胀,浪费的计算。

关于定时器呢?

尽pipe“定时器”是上面列出的默认队列之一,但Ember只能在RunLooptesting用例中引用队列。 似乎这样一个队列将被用于SproutCore天,基于上述文章中的一些描述,关于定时器是最后一件事。 在Ember中,不使用timers队列。 相反,RunLoop可以通过一个内部pipe理的setTimeout事件(参见invokeLaterTimers函数)来分解,这个事件足够智能地循环所有现有的定时器,触发所有已经过期的事件,确定最早的定时器,并设置一个只有该事件的内部setTimeout ,它会在启动RunLoop时再次启动。 这种方法比每个定时器调用setTimeout并唤醒自己更有效率,因为在这种情况下,只需要调用一次setTimeout调用,并且RunLoop足够聪明,可以触发所有不同的定时器时间。

进一步与sync队列反弹

这是运行循环中的一个片段,在循环的中间遍历运行循环中的所有队列。 注意sync队列的特殊情况:因为sync是一个特别易变的队列,在这个队列中,数据在各个方向上传播, Ember.beginPropertyChanges()调用Ember.beginPropertyChanges()来防止任何观察者被触发,随后调用Ember.endPropertyChanges 。 这是明智的:如果在刷新sync队列的过程中,一个对象上的属性完全可能会改变多次,然后才会停止其最终值,并且您不希望浪费资源更改。

 if (queueName === 'sync') { log = Ember.LOG_BINDINGS; if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); } Ember.beginPropertyChanges(); Ember.tryFinally(tryable, Ember.endPropertyChanges); if (log) { Ember.Logger.log('End: Flush Sync Queue'); } } else { forEach.call(queue, iter); } 

希望这可以帮助。 我肯定不得不学一点东西来写这个东西,这很重要。