Update 09.10.2013: Schauen Sie sich diese interaktive Visualisierung der Run-Schleife an: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html
Update 09.05.2013: Alle folgenden Grundkonzepte sind noch auf dem neuesten Stand, aber seit diesem Commit wurde die Ember Run Loop-Implementierung in eine separate Bibliothek namens backburner.js aufgeteilt , mit einigen sehr geringfügigen API-Unterschieden.
Lesen Sie zuerst diese:
http://blog.sproutcore.com/the-run-loop-part-1/
http://blog.sproutcore.com/the-run-loop-part-2/
Sie sind für Ember nicht 100% genau, aber die Kernkonzepte und die Motivation hinter dem RunLoop gelten im Allgemeinen immer noch für Ember. Nur einige Implementierungsdetails unterscheiden sich. Aber weiter zu Ihren Fragen:
Wann startet Ember RunLoop? Ist es abhängig von Router oder Ansichten oder Controllern oder etwas anderem?
Alle grundlegenden Benutzerereignisse (z. B. Tastaturereignisse, Mausereignisse usw.) starten die Ausführungsschleife. Dies garantiert, dass alle Änderungen, die durch das erfasste Ereignis (Maus / Tastatur / Timer / usw.) an gebundenen Eigenschaften vorgenommen wurden, vollständig im gesamten Datenbindungssystem von Ember übertragen werden, bevor die Kontrolle an das System zurückgegeben wird. Wenn Sie also Ihre Maus bewegen, eine Taste drücken, auf eine Schaltfläche klicken usw., wird die Run-Schleife gestartet.
Wie lange dauert es ungefähr (ich weiß, dass es ziemlich albern ist, zu fragen und von vielen Dingen abhängig zu sein, aber ich suche nach einer allgemeinen Idee, oder vielleicht, wenn es eine minimale oder maximale Zeit gibt, die ein Runloop dauern kann)
Der RunLoop wird zu keinem Zeitpunkt nachverfolgen, wie viel Zeit erforderlich ist, um alle Änderungen im System zu verbreiten, und den RunLoop nach Erreichen eines maximalen Zeitlimits anzuhalten. Stattdessen wird RunLoop immer vollständig ausgeführt und erst dann angehalten, wenn alle abgelaufenen Timer aufgerufen, Bindungen weitergegeben und möglicherweise ihre Bindungen weitergegeben wurden und so weiter. Je mehr Änderungen von einem einzelnen Ereignis übernommen werden müssen, desto länger dauert es natürlich, bis RunLoop abgeschlossen ist. Hier ist ein (ziemlich unfaires) Beispiel dafür, wie der RunLoop im Vergleich zu einem anderen Framework (Backbone) ohne Run-Schleife mit sich ausbreitenden Änderungen festgefahren werden kann: http://jsfiddle.net/jashkenas/CGSd5/. Moral der Geschichte: Der RunLoop ist sehr schnell für die meisten Dinge, die Sie jemals in Ember tun möchten, und hier liegt ein Großteil von Embers Macht, aber wenn Sie 30 Kreise mit Javascript mit 60 Bildern pro Sekunde animieren möchten, dann dort Vielleicht sind dies bessere Möglichkeiten, als sich auf Embers RunLoop zu verlassen.
Wird RunLoop zu jeder Zeit ausgeführt oder gibt es nur einen Zeitraum vom Beginn bis zum Ende der Ausführung an und wird möglicherweise einige Zeit nicht ausgeführt.
Es wird nicht immer ausgeführt - es muss irgendwann die Kontrolle an das System zurückgeben, sonst würde Ihre App hängen bleiben - es unterscheidet sich beispielsweise von einer Ausführungsschleife auf einem Server, der eine hat while(true)
und bis unendlich weitergeht Der Server erhält ein Signal zum Herunterfahren ... Der Ember RunLoop hat kein solches Signal, while(true)
sondern wird nur als Reaktion auf Benutzer- / Timer-Ereignisse hochgefahren .
Wenn eine Ansicht aus einer RunLoop heraus erstellt wird, ist dann garantiert, dass der gesamte Inhalt bis zum Ende der Schleife in das DOM gelangt?
Mal sehen, ob wir das herausfinden können. Eine der großen Änderungen von SC zu Ember RunLoop besteht darin, dass Ember anstelle einer Schleife zwischen invokeOnce
und invokeLast
(die Sie im Diagramm im ersten Link zu SproutCores RL sehen) eine Liste von Warteschlangen bereitstellt, die in der Im Verlauf einer Ausführungsschleife können Sie Aktionen (Funktionen, die während der Ausführungsschleife aufgerufen werden sollen) planen, indem Sie angeben, in welche Warteschlange die Aktion gehört (Beispiel aus der Quelle :) Ember.run.scheduleOnce('render', bindView, 'rerender');
.
Wenn Sie sich run_loop.js
den Quellcode ansehen , sehen Sie Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
, aber wenn Sie Ihren JavaScript-Debugger im Browser in einer Ember-App öffnen und auswerten Ember.run.queues
, erhalten Sie eine vollständigere Liste von Warteschlangen : ["sync", "actions", "render", "afterRender", "destroy", "timers"]
. Ember hält ihre Codebasis ziemlich modular und ermöglicht es Ihrem Code sowie seinem eigenen Code in einem separaten Teil der Bibliothek, weitere Warteschlangen einzufügen. In diesem Fall fügt die Ember Views-Bibliothek Einfügungen render
und afterRender
Warteschlangen ein, insbesondere nach der actions
Warteschlange. Ich werde gleich erfahren, warum das so sein könnte. Zunächst der RunLoop-Algorithmus:
Der RunLoop-Algorithmus entspricht weitgehend dem in den obigen Artikeln zur SC-Laufschleife beschriebenen:
- Sie führen Ihren Code zwischen RunLoop aus,
.begin()
und .end()
nur in Ember möchten Sie stattdessen Ihren Code innerhalb von ausführen Ember.run
, der intern begin
und end
für Sie aufgerufen wird . (Nur der interne Run-Loop-Code in der Ember-Codebasis verwendet noch begin
und end
, daher sollten Sie einfach bei bleiben. Ember.run
)
- Nach
end()
dem Aufruf legt der RunLoop einen Gang ein, um jede einzelne Änderung zu übertragen, die durch den an die Ember.run
Funktion übergebenen Codeabschnitt vorgenommen wird . Dies umfasst das Weitergeben der Werte gebundener Eigenschaften, das Rendern von Ansichtsänderungen im DOM usw. usw. Die Reihenfolge, in der diese Aktionen (Binden, Rendern von DOM-Elementen usw.) ausgeführt werden, wird durch das Ember.run.queues
oben beschriebene Array bestimmt :
- Die Ausführungsschleife beginnt in der ersten Warteschlange
sync
. Es werden alle Aktionen ausgeführt, die sync
vom Ember.run
Code in der Warteschlange geplant wurden . Diese Aktionen können selbst auch weitere Aktionen planen, die während derselben RunLoop ausgeführt werden sollen, und es liegt an der RunLoop, sicherzustellen, dass jede Aktion ausgeführt wird, bis alle Warteschlangen geleert sind. Auf diese Weise durchsucht RunLoop am Ende jeder Warteschlange alle zuvor geleerten Warteschlangen und prüft, ob neue Aktionen geplant wurden. In diesem Fall muss es am Anfang der frühesten Warteschlange mit nicht ausgeführten geplanten Aktionen beginnen und die Warteschlange leeren, die Schritte weiter verfolgen und bei Bedarf von vorne beginnen, bis alle Warteschlangen vollständig leer sind.
Das ist die Essenz des Algorithmus. Auf diese Weise werden gebundene Daten über die App weitergegeben. Sie können davon ausgehen, dass alle gebundenen Daten vollständig weitergegeben werden, sobald ein RunLoop vollständig ausgeführt wird. Was ist also mit DOM-Elementen?
Die Reihenfolge der Warteschlangen, einschließlich der von der Ember Views-Bibliothek hinzugefügten, ist hier wichtig. Beachten Sie das render
und afterRender
kommen Sie nach sync
, und action
. Die sync
Warteschlange enthält alle Aktionen zum Weitergeben gebundener Daten. ( action
wird danach in der Ember-Quelle nur noch spärlich verwendet). Basierend auf dem obigen Algorithmus wird garantiert, dass bis zum Eintreffen des RunLoop in der render
Warteschlange alle Datenbindungen synchronisiert sind. Dies ist beabsichtigt: Sie möchten die teure Aufgabe des Renderns von DOM-Elementen vorher nicht ausführenSynchronisieren der Datenbindungen, da dies wahrscheinlich ein erneutes Rendern von DOM-Elementen mit aktualisierten Daten erfordern würde - offensichtlich eine sehr ineffiziente und fehleranfällige Methode zum Leeren aller RunLoop-Warteschlangen. So durchläuft Ember auf intelligente Weise alle erforderlichen Datenbindungsarbeiten, bevor die DOM-Elemente in der render
Warteschlange gerendert werden .
Um Ihre Frage zu beantworten: Ja, Sie können davon ausgehen, dass alle erforderlichen DOM-Renderings bis zum Ende des Vorgangs stattgefunden haben Ember.run
. Hier ist eine jsFiddle zur Demonstration: http://jsfiddle.net/machty/6p6XJ/328/
Weitere wichtige Informationen zum RunLoop
Beobachter gegen Bindungen
Es ist wichtig zu beachten, dass sich Beobachter und Bindungen, obwohl sie die gleiche Funktionalität haben, auf Änderungen in einer "überwachten" Eigenschaft zu reagieren, im Kontext einer RunLoop völlig anders verhalten. Wie wir gesehen haben, wird sync
die Weitergabe von Bindungen in die Warteschlange eingeplant, um schließlich von RunLoop ausgeführt zu werden. Beobachter hingegen werden sofort ausgelöst, wenn sich die überwachte Eigenschaft ändert, ohne dass sie zuerst in eine RunLoop-Warteschlange eingeplant werden müssen. Wenn ein Beobachter und eine Bindung alle dieselbe Eigenschaft "beobachten", wird der Beobachter immer 100% der Zeit vor der Aktualisierung der Bindung aufgerufen.
scheduleOnce
und Ember.run.once
Eine der großen Effizienzsteigerungen in den Vorlagen für die automatische Aktualisierung von Ember basiert auf der Tatsache, dass dank RunLoop mehrere identische RunLoop-Aktionen zu einer einzigen Aktion zusammengeführt ("entprellt" werden können, wenn Sie so wollen). Wenn Sie sich die run_loop.js
Interna ansehen , werden Sie sehen, dass die Funktionen, die dieses Verhalten erleichtern, die zugehörigen Funktionen scheduleOnce
und sind Em.run.once
. Der Unterschied zwischen ihnen ist nicht so wichtig wie das Wissen, dass sie existieren, und wie sie doppelte Aktionen in der Warteschlange verwerfen können, um viele aufgeblähte, verschwenderische Berechnungen während der Ausführungsschleife zu verhindern.
Was ist mit Timern?
Obwohl "Timer" eine der oben aufgeführten Standardwarteschlangen ist, verweist Ember in ihren RunLoop-Testfällen nur auf die Warteschlange. Es scheint, dass eine solche Warteschlange in den SproutCore-Tagen verwendet worden wäre, basierend auf einigen Beschreibungen aus den obigen Artikeln, dass Timer das letzte sind, was ausgelöst wird. In Ember wird die timers
Warteschlange nicht verwendet. Stattdessen kann der RunLoop durch ein intern verwaltetes setTimeout
Ereignis (siehe invokeLaterTimers
Funktion) gestartet werden , das intelligent genug ist, um alle vorhandenen Timer zu durchlaufen, alle abgelaufenen auszulösen, den frühesten zukünftigen Timer zu ermitteln und einen internen festzulegensetTimeout
Nur für dieses Ereignis, bei dem der RunLoop beim Auslösen erneut hochgefahren wird. Dieser Ansatz ist effizienter, als wenn jeder Timer setTimeout aufruft und sich selbst aufweckt, da in diesem Fall nur ein setTimeout-Aufruf ausgeführt werden muss und der RunLoop intelligent genug ist, um alle verschiedenen Timer auszulösen, die möglicherweise gleichzeitig ausgelöst werden Zeit.
Weiteres Entprellen mit der sync
Warteschlange
Hier ist ein Ausschnitt aus der Ausführungsschleife in der Mitte einer Schleife durch alle Warteschlangen in der Ausführungsschleife. Beachten Sie den Sonderfall für die sync
Warteschlange: Da sync
es sich um eine besonders flüchtige Warteschlange handelt, in der Daten in alle Richtungen weitergegeben werden, Ember.beginPropertyChanges()
wird aufgerufen, um zu verhindern, dass Beobachter gefeuert werden, gefolgt von einem Aufruf von Ember.endPropertyChanges
. Dies ist sinnvoll: Wenn sich die sync
Warteschlange während des Leeren der Warteschlange möglicherweise mehrmals ändert, bevor sie sich auf ihren endgültigen Wert stützt, möchten Sie keine Ressourcen verschwenden, indem Sie bei jeder einzelnen Änderung sofort Beobachter entlassen .
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);
}
Hoffe das hilft. Ich musste definitiv einiges lernen, nur um dieses Ding zu schreiben, was irgendwie der Punkt war.