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 的强大功能所在,但是如果您发现自己想用 Javascript 以每秒 60 帧的速度制作 30 个圆圈,那么可能比依赖 Ember 的 RunLoop 更好。
RunLoop 是一直在执行,还是只是表示从执行开始到结束的一段时间,可能有一段时间不运行。
它不是一直执行的——它必须在某个时候将控制权返回给系统,否则你的应用程序会挂起——它不同于服务器上的运行循环,它有一个while(true)
并且一直持续到无穷大直到服务器收到关闭信号...... Ember RunLoop 没有这样的信号,while(true)
但仅在响应用户/计时器事件时启动。
如果一个视图是从一个 RunLoop 中创建的,是否保证在循环结束时它的所有内容都会进入 DOM?
让我们看看我们是否能解决这个问题。从 SC 到 Ember RunLoop 的一个重大变化是invokeOnce
,invokeLast
Ember 为您提供了一个“队列”列表,而不是在在运行循环的过程中,您可以通过指定动作所属的队列来安排动作(在运行循环期间调用的函数)(来自源的示例:)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 中您才希望在 内部运行您的代码Ember.run
,这将在内部为您调用begin
和end
。(只有 Ember 代码库中的内部运行循环代码仍然使用begin
and end
,所以你应该坚持使用Ember.run
)
- 在
end()
被调用之后,RunLoop 就会启动以传播由传递给Ember.run
函数的代码块所做的每一个更改。这包括传播绑定属性的值、将视图更改渲染到 DOM 等。这些操作(绑定、渲染 DOM 元素等)的执行顺序由上述Ember.run.queues
数组确定:
- 运行循环将从第一个队列开始,即
sync
. 它将运行代码安排到sync
队列中的所有操作Ember.run
。这些动作本身也可能在同一个 RunLoop 期间安排更多动作执行,并且由 RunLoop 确保它执行每个动作,直到所有队列都被刷新。它这样做的方式是,在每个队列的末尾,RunLoop 将查看所有先前刷新的队列,并查看是否已安排任何新操作。如果是这样,它必须从具有未执行的预定操作的最早队列的开头开始并清除队列,继续跟踪其步骤并在必要时重新开始,直到所有队列完全为空。
这就是算法的本质。这就是绑定数据通过应用程序传播的方式。您可以预期,一旦 RunLoop 运行完成,所有绑定的数据都将完全传播。那么,DOM 元素呢?
队列的顺序,包括由 Ember 视图库添加的队列,在这里很重要。注意render
和afterRender
之后sync
, 和action
。队列包含传播绑定数据的sync
所有动作。(action
之后,仅在 Ember 源代码中很少使用)。基于上述算法,可以保证在 RunLoop 进入render
队列时,所有数据绑定都已完成同步。这是设计使然:您不希望在之前执行渲染 DOM 元素的昂贵任务同步数据绑定,因为这可能需要使用更新的数据重新渲染 DOM 元素——显然是清空所有 RunLoop 队列的一种非常低效且容易出错的方法。render
因此,Ember 在渲染队列中的 DOM 元素之前智能地完成所有数据绑定工作。
所以,最后,要回答你的问题,是的,你可以预期到完成时任何必要的 DOM 渲染都会发生Ember.run
。这是一个 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
. 它们之间的区别并不像知道它们存在那么重要,以及它们如何丢弃队列中的重复操作以防止在运行循环期间进行大量臃肿、浪费的计算。
计时器呢?
尽管 'timers' 是上面列出的默认队列之一,但 Ember 仅在其 RunLoop 测试用例中引用了该队列。根据上述文章中关于计时器是最后触发的一些描述,在 SproutCore 时代似乎会使用这样的队列。在 Ember 中,timers
不使用队列。相反,RunLoop 可以由内部管理的setTimeout
事件(见invokeLaterTimers
函数)启动,它足够智能,可以循环所有现有的计时器,触发所有过期的计时器,确定最早的未来计时器,并设置一个内部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);
}
希望这可以帮助。为了写这个东西,我肯定需要学习很多东西,这很重要。