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的数据绑定系统中完全传播。因此,移动鼠标,按下键,单击按钮等,都将启动运行循环。
大概需要多长时间(我知道这很愚蠢,并且要取决于很多事情,但是我正在寻找一个一般性的想法,或者是否可能需要最小或最大的运行循环时间)
RunLoop绝不会跟踪花费所有时间在系统中传播所有更改,然后在达到最大时间限制后停止RunLoop;确切地说,RunLoop将始终运行到完成,并且直到所有已到期的计时器都已被调用,绑定被传播,以及它们的绑定被传播等等,都不会停止。显然,从单个事件传播的更改越多,RunLoop完成所需的时间就越长。这是一个(非常不公平的)示例,与没有运行循环的另一个框架(主干)相比,RunLoop如何陷入繁琐的更改中:http : //jsfiddle.net/jashkenas/CGSd5/。故事的寓意:RunLoop确实可以在Ember中完成大多数事情,而这正是Ember强大功能的所在,但是如果您发现自己想以每秒60帧的速度用Javascript动画30个圆,那么可能比依靠Ember的RunLoop更好。
RunLoop是否一直在执行,还是只是指示从开始执行到结束执行的时间,可能不会运行一段时间。
它并非始终执行-它必须在某个时刻将控制权返回给系统,否则您的应用会挂起-它不同于服务器上的运行循环,该循环具有a while(true)
并持续无限,直到服务器收到关闭信号。Ember RunLoop没有关闭信号,while(true)
只是响应用户/计时器事件而启动。
如果从一个RunLoop内部创建视图,是否可以保证在循环结束时所有视图的内容都将进入DOM?
让我们看看是否可以解决这个问题。一个从SC到灰烬RunLoop大的变化是,而不是循环来回之间invokeOnce
和invokeLast
(你约了SproutCore的RL的第一个链接图中看到),灰烬为您提供“队列”的列表,在在运行循环过程中,您可以通过指定动作所属的队列来调度动作(在运行循环中要调用的函数)(例如,来自源:)Ember.run.scheduleOnce('render', bindView, 'rerender');
。
如果您查看run_loop.js
源代码,则会看到Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
,但是如果您在Ember应用程序的浏览器中打开JavaScript调试器并进行评估Ember.run.queues
,则会得到更完整的队列列表:["sync", "actions", "render", "afterRender", "destroy", "timers"]
。Ember保持其代码库相当模块化,它们使您的代码以及库中单独部分中的代码可以插入更多队列。在这种情况下,Ember Views库将插入render
并插入afterRender
队列,尤其是在actions
队列之后。我将解释为什么可能要在一秒钟之后。首先,RunLoop算法:
RunLoop算法与以上SC运行循环文章中所述的算法几乎相同:
- 您运行RunLoop之间的代码
.begin()
和.end()
,只有在灰烬你会想,而不是运行在您的代码Ember.run
,这将在内部调用begin
,并end
为您服务。(只有Ember代码库中的内部运行循环代码仍使用begin
和end
,因此您应该坚持使用Ember.run
)
- 之后
end()
被调用,然后RunLoop踢成齿轮传播通过代码传递给块每一个微小变动Ember.run
的功能。这包括传播绑定属性的值,渲染对DOM的视图更改等,这些动作(绑定,渲染DOM元素等)的执行顺序由上述Ember.run.queues
数组确定:
- 运行循环将在第一个队列上开始
sync
。它将运行sync
由Ember.run
代码安排到队列中的所有操作。这些动作本身也可以安排在同一RunLoop期间执行更多的动作,这取决于RunLoop来确保它执行所有动作,直到刷新所有队列为止。这样做的方法是,在每个队列的末尾,RunLoop将浏览所有以前刷新的队列,并查看是否已安排任何新操作。如果是这样,它必须从最早的队列开始,执行未执行的计划动作,然后清除该队列,继续跟踪其步骤,并在必要时重新开始,直到所有队列完全为空。
这就是算法的本质。这就是绑定数据通过应用程序传播的方式。您可以期望,一旦RunLoop运行完成,所有绑定的数据都将被完全传播。那么,DOM元素呢?
队列的顺序(包括Ember Views库添加的队列)在这里很重要。请注意render
和afterRender
之后sync
,以及action
。该sync
队列包含了所有传播绑定数据的操作。(action
之后,仅在Ember来源中很少使用)。基于以上算法,可以保证在RunLoop到达render
队列时,所有数据绑定都将完成同步。这是设计使然:您不想在渲染DOM元素之前执行昂贵的任务同步数据绑定,因为这可能需要使用更新的数据重新呈现DOM元素-显然,清空所有RunLoop队列是一种非常低效且容易出错的方式。因此,Ember在呈现render
队列中的DOM元素之前,会智能地完成所有数据绑定工作。
因此,最后,要回答您的问题,是的,您可以期望在Ember.run
完成时会进行任何必要的DOM渲染。这是一个演示的jsFiddle:http : //jsfiddle.net/machty/6p6XJ/328/
有关RunLoop的其他注意事项
观察者与绑定
重要的是要注意,观察者和绑定虽然具有响应“监视”属性中的更改的类似功能,但在RunLoop的上下文中却表现完全不同。如我们所见,绑定传播被调度到sync
队列中,最终由RunLoop执行。另一方面,观察者的属性发生更改时,观察者会立即触发,而不必先将其调度到RunLoop队列中。如果观察者和绑定都“监视”了相同的属性,则在更新绑定之前,观察者将始终被100%的时间调用。
scheduleOnce
和 Ember.run.once
Ember自动更新模板的最大效率提升之一是基于这样一个事实,即借助RunLoop,可以将多个相同的RunLoop操作合并(如果需要,可以“反跳”)为一个操作。如果您查看run_loop.js
内部原理,您会看到促进此行为的功能是相关功能scheduleOnce
和Em.run.once
。它们之间的区别并不重要,要知道它们的存在,以及它们如何在队列中丢弃重复的操作以防止运行循环中大量肿,浪费的计算。
计时器呢?
即使“计时器”是上面列出的默认队列之一,Ember也仅在其RunLoop测试用例中引用该队列。根据上述文章中有关定时器是最后要触发的事情的一些描述,似乎在SproutCore时代中会使用这种队列。在Ember中,timers
不使用队列。相反,可以通过内部管理的setTimeout
事件(请参见invokeLaterTimers
函数)来启动RunLoop,该事件足够智能以遍历所有现有计时器,触发所有已到期的计时器,确定最早的将来计时器并设置内部计时器。setTimeout
仅针对该事件,它将在运行时再次启动RunLoop。这种方法比让每个计时器调用setTimeout并自动唤醒的效率更高,因为在这种情况下,只需要进行一次setTimeout调用,并且RunLoop足够聪明,可以触发所有可能同时关闭的不同计时器时间。
与sync
队列进一步消除抖动
这是运行循环中的一个片段,位于运行循环中所有队列的循环中间。请注意该sync
队列的特殊情况:因为sync
是一个特别易变的队列,在该队列中数据在各个方向上传播,所以它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);
}
希望这可以帮助。我绝对必须学到很多东西才能写这个东西,这很重要。