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

时间:2012-11-28 04:32:36

标签: ember.js

我试图了解Ember RunLoop是如何工作的以及是什么让它成为现实。我查看了the documentation,但仍然有很多问题。我有兴趣更好地理解RunLoop的工作原理,所以我可以在其名称空间中选择适当的方法,当我不得不推迟执行某些代码时。

  • Ember RunLoop何时开始。它是依赖于路由器,视图或控制器还是别的什么?
  • 它需要多长时间(我知道这很愚蠢,要求并依赖于许多事情,但我正在寻找一个大概的想法,或者如果有一个最小或最大的时间,可以采取一个runloop)
  • RunLoop是否一直在执行,或者只是表示从开始到结束执行的一段时间,可能不会运行一段时间。
  • 如果从一个RunLoop中创建一个视图,是否保证在循环结束时它的所有内容都会进入DOM?

请原谅我,如果这些是非常基本的问题,我认为了解这些将有助于像我这样的新人更好地使用Ember。

1 个答案:

答案 0 :(得分:199)

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

2013年5月9日更新:以下所有基本概念仍然是最新的,但自this commit起,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完成所需的时间越长。这是一个(相当不公平)的例子,说明与另一个没有运行循环的框架({3}}相比,RunLoop如何通过传播变化陷入困境。故事的道德:RunLoop对于你在Ember中想要做的大多数事情来说真的很快,而且Ember的力量所在的地方很多,但如果你发现自己想要使用Javascript以每秒60帧的速度为30个圆圈制作动画,可能有更好的方法来实现它,而不是依赖于Ember的RunLoop。

RunLoop是否始终执行,或者它只是指示从开始到结束执行的一段时间,可能不会运行一段时间。

它不会一直执行 - 它必须在某个时刻将控制权返还给系统,否则你的应用程序会挂起 - 它与服务器上的运行循环不同a while(true)并继续无限,直到服务器收到关闭的信号...... Ember RunLoop没有这样的while(true)但只是为了响应用户/计时器事件而旋转。

如果从一个RunLoop中创建一个视图,是否保证在循环结束时它的所有内容都会进入DOM?

让我们看看我们是否可以解决这个问题。从SC到Ember RunLoop的一个重大变化是,而不是在invokeOnceinvokeLast之间来回循环(您在第一个链接中的图表中看到SproutCore' s RL) ,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"]更完整的队列列表:render。 Ember使其代码库非常模块化,并且它们使您的代码以及库中单独部分的代码可以插入更多队列。在这种情况下,Ember Views库会插入afterRenderactions队列,特别是在.begin()队列之后。我会在一秒钟之内找到原因。首先,RunLoop算法:

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

  • 您在RunLoop .end()Ember.run之间运行代码,仅在Ember中,您希望在begin内运行您的代码,这将在内部调用{{1} }和end给你。 (Ember代码库中的内部运行循环代码仍然使用beginend,因此您应该坚持使用Ember.run
  • 调用end()之后,RunLoop随后开始传播传递给Ember.run函数的代码块所做的每一次更改。这包括传播绑定属性的值,将视图更改呈现给DOM等等。执行这些操作(绑定,呈现DOM元素等)的顺序由上述Ember.run.queues数组确定:< / LI>
  • 运行循环将从第一个队列开始,即sync。它将运行sync代码计划到Ember.run队列中的所有操作。这些操作本身也可以安排在同一个RunLoop期间执行更多操作,并且由RunLoop确保它执行每个操作直到刷新所有队列。它执行此操作的方式是,在每个队列的末尾,RunLoop将查看所有先前刷新的队列,并查看是否已安排任何新操作。如果是这样,它必须在最早的队列开始时使用未执行的调度操作并清空队列,继续跟踪其步骤并在必要时重新开始直到所有队列完全为空。

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

队列的顺序,包括由Ember Views库添加的队列,在这里很重要。请注意,renderafterRender来自syncactionsync队列包含传播绑定数据的所有操作。 (action,之后,在Ember源中仅被稀疏地使用)。基于上述算法,可以保证在RunLoop到达render队列时,所有数据绑定都将完成同步。这是设计的:在同步数据绑定之前,您不希望执行渲染DOM元素的昂贵任务,因为这可能需要重新呈现DOM元素更新的数据 - 显然是一种非常低效且容易出错的清空所有RunLoop队列的方法。因此,在render队列中呈现DOM元素之前,Ember会智能地完成所有数据绑定工作。

所以,最后,回答你的问题,是的,你可以预期任何必要的DOM渲染都会在Ember.run完成之前发生。这是一个证明:http://jsfiddle.net/jashkenas/CGSd5/

的jsFiddle

了解RunLoop

的其他事项

Observers vs. Bindings

重要的是要注意Observers和Bindings,同时具有类似的功能来响应&#34;观看&#34;属性,在RunLoop的上下文中表现完全不同。正如我们所见,绑定传播被调度到sync队列中,最终由RunLoop执行。另一方面,观察者在监视属性发生变化时立即激活,而不必首先调度到RunLoop队列。如果一个观察者和一个绑定所有&#34;观看&#34;相同的属性,观察者将始终比绑定更新时的100%被调用。

scheduleOnceEmber.run.once

Ember自动更新模板的一大效率提升基于以下事实:由于RunLoop,可以合并多个相同的RunLoop操作(&#34;去抖动&#34;,如果你将)变成一个单一的行动。如果您查看run_loop.js内幕,您会看到促成此行为的函数是相关函数scheduleOnceEm.run.once。它们之间的区别并不重要,因为它们知道它们存在,以及它们如何在队列中丢弃重复的动作以防止在运行循环期间进行大量繁琐,浪费的计算。

计时器怎么样?

尽管&#39;计时器&#39;是上面列出的默认队列之一,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);
}

希望这会有所帮助。我一定要学习写这个东西,这有点重要。