96

Ember RunLoop がどのように機能し、何がそれを動かしているのかを理解しようとしています。ドキュメントを見ましたが、まだ多くの質問があります。RunLoop がどのように機能するかをよりよく理解することに興味があります。これにより、一部のコードの実行を後で延期する必要がある場合に、その名前空間内で適切なメソッドを選択できるようになります。

  • Ember RunLoop はいつ開始しますか。ルーター、ビュー、コントローラーなどに依存していますか?
  • おおよそどのくらいの時間がかかりますか(これは尋ねるのがかなりばかげており、多くのことに依存していることは知っていますが、一般的なアイデアを探しているか、実行ループにかかる最小時間または最大時間があるかどうか)
  • RunLoop は常に実行されていますか、それとも実行の開始から終了までの期間を示しているだけで、しばらく実行されない可能性があります。
  • ビューが 1 つの RunLoop 内から作成された場合、ループが終了するまでにすべてのコンテンツが DOM になることが保証されますか?

これらが非常に基本的な質問である場合はご容赦ください。これらを理解することで、私のような初心者が Ember をより適切に使用できるようになると思います。

4

1 に答える 1

198

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 は常に最後まで実行され、期限切れのタイマーがすべて呼び出され、バインディングが伝達され、おそらくそれらのバインディングが伝達されるまで停止しません。明らかに、1 つのイベントから伝播する必要がある変更が多いほど、RunLoop が終了するまでの時間が長くなります。以下は、実行ループを持たない別のフレームワーク (バックボーン) と比較して、RunLoop が変更の伝播で行き詰まる (かなり不公平な) 例です: http://jsfiddle.net/jashkenas/CGSd5/. 話の教訓: RunLoop は、Ember でやりたいことのほとんどで非常に高速であり、Ember のパワーの多くはそこにあります。 Ember の RunLoop に頼るよりも良い方法かもしれません。

RunLoop は常に実行されていますか、それとも実行の開始から終了までの期間を示しているだけで、しばらく実行されない可能性があります。

while(true)常に実行されるわけではありません。ある時点で制御をシステムに戻す必要があります。そうしないと、アプリがハングします。たとえば、サーバー上の実行ループとは異なります。サーバーはシャットダウンするシグナルを受け取ります... Ember RunLoopにはそのようなwhile(true)ものはありませんが、ユーザー/タイマーイベントに応答してのみスピンアップされます。

ビューが 1 つの RunLoop 内から作成された場合、ループが終了するまでにすべてのコンテンツが DOM になることが保証されますか?

それを理解できるかどうか見てみましょう。SC から Ember RunLoop への大きな変更点の 1 つは、(SproutCore の RL に関する最初のリンクの図にあるように)invokeOnceとの間をループする代わりに、Ember が「キュー」のリストを提供することです。invokeLast実行ループの過程で、アクションが属するキューを指定することで、アクション (実行ループ中に呼び出される関数) をスケジュールできます (ソースの例: 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してキューに入れます。その理由はすぐにわかります。まず、RunLoop アルゴリズム:afterRenderactions

RunLoop アルゴリズムは、上記の SC 実行ループの記事で説明されているものとほとんど同じです。

  • .begin()RunLoopとの間でコードを実行します。Ember.end()でのみ、代わりに 内でコードを実行する必要があります。これは内部的にとをEmber.run呼び出します。(Ember コード ベースの内部実行ループ コードのみが引き続きandを使用するため、そのまま使用する必要があります)beginendbeginendEmber.run
  • が呼び出された後end()、RunLoop がギアを入れて、Ember.run関数に渡されたコードのチャンクによって行われたすべての変更を伝播します。これには、バインドされたプロパティの値の伝播、ビューの変更の DOM へのレンダリングなどが含まれます。これらのアクション (バインディング、DOM 要素のレンダリングなど) が実行される順序は、Ember.run.queues上記の配列によって決まります。
  • 実行ループは、最初のキューである で開始されますsyncsyncコードによってキューにスケジュールされたすべてのアクションが実行されEmber.runます。これらのアクション自体も、この同じ RunLoop 中に実行される追加のアクションをスケジュールする場合があり、すべてのキューがフラッシュされるまですべてのアクションを確実に実行するのは RunLoop 次第です。これを行う方法は、すべてのキューの最後に、RunLoop が以前にフラッシュされたすべてのキューを調べて、新しいアクションがスケジュールされているかどうかを確認することです。その場合、スケジュールされたアクションが実行されていない最も古いキューの先頭から開始し、キューをフラッシュして、すべてのキューが完全に空になるまで、その手順を追跡し、必要に応じて最初からやり直す必要があります。

それがアルゴリズムの本質です。これが、バインドされたデータがアプリを通じて伝播される方法です。RunLoop が完了するまで実行されると、バインドされたすべてのデータが完全に伝播されることが期待できます。では、DOM 要素はどうでしょうか。

ここでは、Ember Views ライブラリによって追加されたキューを含むキューの順序が重要です。と の後に続くことに注意してrenderください。キューには、バインドされたデータを伝播するためのすべてのアクションが含まれています。( 、その後、Ember ソースではまばらにしか使用されません)。上記のアルゴリズムに基づいて、RunLoop がキューに到達するまでに、すべてのデータ バインディングの同期が完了することが保証されます。これは仕様によるものですafterRendersyncactionsyncactionrenderデータ バインディングを同期します。更新されたデータを使用して DOM 要素を再レンダリングする必要がある可能性が高いためです。明らかに、すべての RunLoop キューを空にする非常に非効率的でエラーが発生しやすい方法です。そのため、Ember は、renderキュー内の DOM 要素をレンダリングする前に、可能なすべてのデータ バインディング作業をインテリジェントに実行します。

最後に、あなたの質問に答えるために、はい、Ember.run終了するまでに必要な DOM レンダリングが行われることを期待できます。デモ用の jsFiddle を次に示します: http://jsfiddle.net/machty/6p6XJ/328/

RunLoop について知っておくべきその他のこと

オブザーバーとバインディング

Observers と Bindings は、「監視された」プロパティの変更に応答するという同様の機能を持っていますが、RunLoop のコンテキストではまったく異なる動作をすることに注意することが重要です。これまで見てきたように、バインドの伝播はsyncキューにスケジュールされ、最終的に RunLoop によって実行されます。一方、オブザーバーは、最初に RunLoop キューにスケジュールする必要なく、監視対象のプロパティが変更されるとすぐに起動します。オブザーバーとバインディングがすべて同じプロパティを「監視」する場合、オブザーバーはバインディングが更新される前に常に 100% 呼び出されます。

scheduleOnceEmber.run.once

Ember の自動更新テンプレートの大幅な効率向上の 1 つは、RunLoop のおかげで、複数の同一の RunLoop アクションを 1 つのアクションに結合 (できれば「デバウンス」) できるという事実に基づいています。内部を調べると、この動作を容易にする関数が関連する関数とrun_loop.jsであることがわかります。それらの違いは、それらが存在すること、および実行ループ中に多くの肥大化した無駄な計算を防ぐためにキュー内の重複するアクションを破棄する方法ほど重要ではありません。scheduleOnceEm.run.once

タイマーはどうですか?

「タイマー」は上記のデフォルト キューの 1 つですが、Ember は RunLoop テスト ケースでのみキューを参照します。このようなキューは、タイマーが最後に起動するという上記の記事の説明に基づいて、SproutCore の時代に使用されていたようです。Ember では、timersキューは使用されません。代わりに、RunLoop は、内部で管理されるsetTimeoutイベント (invokeLaterTimers関数を参照) によってスピンアップできます。このイベントは、既存のすべてのタイマーをループし、期限切れになったすべてのタイマーを開始し、最も早い将来のタイマーを決定し、内部タイマーを設定するのに十分なほどインテリジェントです。setTimeoutそのイベントに対してのみ、実行時に RunLoop が再びスピンアップします。このアプローチは、各タイマーが setTimeout を呼び出してそれ自体を起動するよりも効率的です。この場合、必要な setTimeout 呼び出しは 1 つだけであり、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);
}

お役に立てれば。これを書くためだけにかなりのことを学ばなければならなかったのは間違いなく、それがポイントのようなものでした。

于 2013-01-12T18:07:52.030 に答える