什么是Ember RunLoop?它是如何工作的?
我正在尝试了解Ember RunLoop的工作原理,以及它是如何工作的。 我已经看过文档,但仍然有很多关于它的问题。 我有兴趣更好地理解RunLoop的工作原理,以便我可以在其名称空间中选择合适的方法,以便稍后推迟执行某些代码。
如果这些都是非常基本的问题,请原谅我,我认为理解这些将有助于像我这样的新手更好地使用Ember。
2013年10月9日更新:查看运行循环的交互式可视化:https: //machty.s3.amazonaws.com/ember-run-loop-visual/index.html
2013年5月9日更新:下面的所有基本概念仍然是最新的,但就此提交而言,Ember运行循环实现已经拆分为一个名为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将花费的时间越长。 下面是一个非常不公平的例子,说明RunLoop与没有运行循环的另一个框架(Backbone)相比,可能会因传播变化而陷入停滞状态:http://jsfiddle.net/jashkenas/CGSd5/。 这个故事的寓意是:RunLoop对于你在Ember中想要做的大部分事情来说非常快速,而且这也是Ember的力量所在,但是如果你发现自己想要以60帧/秒的速度用Javascript动画30个圈子,那么在那里可能会比依靠Ember的RunLoop更好地实现它。
RunLoop在任何时候都被执行,或者它只是表示从开始到执行结束的一段时间,并且可能不会运行一段时间。
它在任何时候都不会被执行 - 它必须在某个时候将控制权还给系统,否则你的应用程序将会挂起 - 这与在服务器上运行一段while(true)
并且运行循环不同直到服务器收到关闭的信号为止...... Ember RunLoop没有这样的while(true)
但是只响应用户/定时器事件而旋转。
如果一个视图是在一个RunLoop中创建的,它是否可以保证它的所有内容都会在循环结束时将其放入DOM中?
让我们看看我们是否可以弄清楚。 从SC到Ember RunLoop的一个重大变化是,Ember不是在invokeOnce
和invokeLast
之间来回循环(在第一个链接关于SproutCore的RL的图中可以看到),Ember为您提供了一个“队列”列表,在运行循环过程中,您可以通过指定动作所属的哪个队列(例如来自源代码: Ember.run.scheduleOnce('render', bindView, 'rerender');
)来计划动作(在运行循环期间调用的函数) 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库会在actions
队列之后插入render
和afterRender
队列。 我会解释为什么可能在一秒钟之内。 首先,RunLoop算法:
RunLoop算法与上面的SC运行循环文章中描述的几乎相同:
.begin()
和.end()
之间运行代码,只有在Ember中,您需要在Ember.run
运行代码,该代码将在内部为您调用begin
和end
。 (只有Ember代码库中的内部运行循环代码仍然使用begin
和end
,所以你应该坚持使用Ember.run
) end()
之后,RunLoop开始启动,传播由传递给Ember.run
函数的代码块所做的每一个更改。 这包括传播绑定属性的值,将视图更改渲染到DOM等等。这些操作(绑定,呈现DOM元素等)的执行Ember.run.queues
由上面描述的Ember.run.queues
数组确定: sync
的第一个队列开始。 它将运行由Ember.run
代码安排到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
队列。 相反,RunLoop可以通过内部管理的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);
}
希望这可以帮助。 我肯定不得不学一点东西来写这个东西,这很重要。
链接地址: http://www.djcxy.com/p/65809.html上一篇: What is Ember RunLoop and how does it work?
下一篇: how to implement not with if statement in ember handlebar?