J'essaie de comprendre comment Ember RunLoop fonctionne et ce qui le fait fonctionner. J'ai regardé la documentation , mais j'ai encore beaucoup de questions à ce sujet. Je suis intéressé par mieux comprendre comment RunLoop fonctionne afin que je puisse choisir la méthode appropriée dans son espace de nom, quand je dois différer l'exécution de certains codes pour une date ultérieure.
Pardonnez-moi si ce sont des questions très basiques, je pense que les comprendre aidera les noobs comme moi à mieux utiliser Ember.
Mise à jour 10/9/2013: Découvrez cette visualisation interactive de la boucle d'exécution: https://machty.s3.amazonaws.com /ember-run-loop-visual/index.html
Mise à jour 5/9/2013: tous les concepts de base ci-dessous sont toujours à jour, mais en date de ce commit , le Ember L'implémentation de la boucle d'exécution a été divisée en une bibliothèque distincte appelée backburner.js , avec quelques différences API très mineures.
Tout d'abord, lisez-les:
http://blog.sproutcore.com/the-run-loop-part-1/
http://blog.sproutcore.com/the-run-loop-part-2/
Ils ne sont pas précis à 100% pour Ember, mais les concepts de base et la motivation derrière le RunLoop s'appliquent généralement à Ember; seuls certains détails de mise en œuvre diffèrent. Mais, pour vos questions:
Tous les événements utilisateur de base (par exemple, événements clavier, événements souris, etc.) déclencheront la boucle d'exécution. Cela garantit que toutes les modifications apportées aux propriétés liées par l'événement capturé (souris/clavier/minuterie/etc.) sont entièrement propagées à travers le système de liaison de données d'Ember avant de retourner le contrôle au système. Donc, en déplaçant votre souris, en appuyant sur une touche, en cliquant sur un bouton, etc., tout lance la boucle de course.
À aucun moment, le RunLoop ne gardera trace du temps qu'il faut pour propager toutes les modifications à travers le système, puis arrêter le RunLoop après avoir atteint une limite de temps maximale; au lieu de cela, RunLoop s'exécutera toujours jusqu'à son terme et ne s'arrêtera pas tant que tous les temporisateurs expirés n'auront pas été appelés, les liaisons propagées, et peut-être leur les liaisons propagées, etc. De toute évidence, plus il y a de changements à propager à partir d'un seul événement, plus le RunLoop mettra longtemps à se terminer. Voici un exemple (assez injuste) de la façon dont le RunLoop peut s'enliser avec la propagation des modifications par rapport à un autre framework (Backbone) qui n'a pas de boucle d'exécution: http://jsfiddle.net/jashkenas/CGSd5/ . Morale de l'histoire: le RunLoop est très rapide pour la plupart des choses que vous voudriez faire dans Ember, et c'est là que réside la plus grande partie de la puissance d'Ember, mais si vous vous trouvez à vouloir animer 30 cercles avec Javascript à 60 images par seconde, là pourrait être de meilleures façons de s'y prendre que de compter sur RunLoop d'Ember.
Elle n'est pas exécutée à tout moment - elle doit rendre le contrôle au système à un moment ou sinon votre application se bloquerait - c'est différent, disons, d'une boucle d'exécution sur un serveur qui a un while(true)
et continue à l'infini jusqu'à ce que le serveur reçoive un signal pour s'arrêter ... le Ember RunLoop n'a pas de tel while(true)
mais est uniquement tourné en réponse aux événements utilisateur/timer) .
Voyons voir si nous pouvons comprendre cela. L'un des grands changements de SC à Ember RunLoop est que, au lieu de faire des allers-retours entre invokeOnce
et invokeLast
(que vous voyez dans le diagramme dans le premier lien sur le RL de SproutCore), Ember vous fournit une liste de 'files d'attente' qui, au cours d'une boucle d'exécution, vous pouvez planifier des actions ( fonctions à appeler pendant la boucle d'exécution) en spécifiant dans quelle file d'attente l'action appartient (exemple de la source: Ember.run.scheduleOnce('render', bindView, 'rerender');
).
Si vous regardez run_loop.js
Dans le code source, vous voyez Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
, Mais si vous ouvrez votre débogueur JavaScript dans le navigateur dans une application Ember et évaluez Ember.run.queues
, Vous obtenez une liste plus complète de files d'attente: ["sync", "actions", "render", "afterRender", "destroy", "timers"]
. Ember conserve leur base de code assez modulaire, et ils rendent possible votre code, ainsi que son propre code dans une partie distincte de la bibliothèque, pour insérer plus de files d'attente. Dans ce cas, la bibliothèque Ember Views insère les files d'attente render
et afterRender
, en particulier après la file d'attente actions
. Je vais comprendre pourquoi cela pourrait se produire dans une seconde. Premièrement, l'algorithme RunLoop:
L'algorithme RunLoop est à peu près le même que celui décrit dans les articles sur la boucle d'exécution SC ci-dessus:
.begin()
et .end()
, uniquement en Ember vous voudrez plutôt exécuter votre code dans Ember.run
, Qui appellera en interne begin
et end
pour vous. (Seul le code de boucle d'exécution interne dans la base de code Ember utilise toujours begin
et end
, vous devriez donc vous en tenir à Ember.run
)end()
, le RunLoop passe alors à la vitesse supérieure pour propager chaque modification apportée par le bloc de code passé à la fonction Ember.run
. Cela inclut la propagation des valeurs des propriétés liées, le rendu des modifications de vue dans le DOM, etc. etc. L'ordre dans lequel ces actions (liaison, rendu des éléments DOM, etc.) sont effectuées est déterminé par le tableau Ember.run.queues
Décrit ci-dessus:sync
. Il exécutera toutes les actions planifiées dans la file d'attente sync
par le code Ember.run
. Ces actions peuvent elles-mêmes également planifier d'autres actions à exécuter au cours de cette même RunLoop, et c'est à la RunLoop de s'assurer qu'elle exécute chaque action jusqu'à ce que toutes les files d'attente soient vidées. À la fin de chaque file d'attente, le RunLoop examine toutes les files d'attente précédemment vidées et vérifie si de nouvelles actions ont été planifiées. Si tel est le cas, il doit démarrer au début de la première file d'attente avec des actions planifiées non exécutées et vider la file d'attente, en continuant de suivre ses étapes et recommencer si nécessaire jusqu'à ce que toutes les files d'attente soient complètement vides.Voilà l'essence de l'algorithme. C'est ainsi que les données liées sont propagées via l'application. Vous pouvez vous attendre à ce qu'une fois RunLoop terminé, toutes les données liées soient entièrement propagées. Alors, qu'en est-il des éléments DOM?
L'ordre des files d'attente, y compris celles ajoutées par la bibliothèque Ember Views, est important ici. Notez que render
et afterRender
viennent après sync
et action
. La file d'attente sync
contient toutes les actions pour propager les données liées. (action
, après cela, n'est que rarement utilisé dans le Ember source). D'après l'algorithme ci-dessus, il est garanti qu'au moment où RunLoop arrivera dans la file d'attente render
, toutes les liaisons de données auront terminé la synchronisation. Ceci est voulu par la conception même du produit. : vous ne voudriez pas effectuer la tâche coûteuse de rendu des éléments DOM avant synchroniser les liaisons de données, car cela nécessiterait probablement un nouveau rendu des éléments DOM avec des données mises à jour - évidemment un manière très inefficace et sujette aux erreurs de vider toutes les files d'attente RunLoop. Donc Ember explose intelligemment tout le travail de liaison de données qu'il peut avant de rendre les éléments DOM dans le render
queue.
Donc, enfin, pour répondre à votre question, oui, vous pouvez vous attendre à ce que tous les rendus DOM nécessaires aient eu lieu au moment où Ember.run
Se termine. Voici un jsFiddle pour démontrer: http://jsfiddle.net/machty/6p6XJ/328/
Il est important de noter que les observateurs et les liaisons, tout en ayant la fonctionnalité similaire de répondre aux changements dans une propriété "surveillée", se comportent totalement différemment dans le contexte d'un RunLoop. La propagation de liaison, comme nous l'avons vu, est planifiée dans la file d'attente sync
pour être éventuellement exécutée par RunLoop. Les observateurs, d'autre part, tirent immédiatement lorsque la propriété surveillée change sans avoir à être programmée pour la première fois dans une file d'attente RunLoop. Si un observateur et une liaison "surveillent" tous la même propriété, l'observateur sera toujours appelé 100% du temps avant que la liaison ne soit mise à jour.
scheduleOnce
et Ember.run.once
L'un des gros gains d'efficacité dans les modèles de mise à jour automatique d'Ember est basé sur le fait que, grâce à RunLoop, plusieurs actions RunLoop identiques peuvent être fusionnées ("rebondies", si vous voulez) en une seule action. Si vous examinez les internes run_loop.js
, Vous verrez que les fonctions qui facilitent ce comportement sont les fonctions connexes scheduleOnce
et Em.run.once
. La différence entre eux n'est pas aussi importante que de savoir qu'ils existent et de savoir comment ils peuvent ignorer les actions en double dans la file d'attente pour éviter beaucoup de calculs gonflés et inutiles pendant la boucle d'exécution.
Même si 'timers' est l'une des files d'attente par défaut répertoriées ci-dessus, Ember fait uniquement référence à la file d'attente dans leurs cas de test RunLoop. Il semble qu'une telle file d'attente aurait été utilisée dans les jours SproutCore basé sur certaines des descriptions des articles ci-dessus sur les minuteries étant la dernière chose à déclencher. Dans Ember, la file d'attente timers
n'est pas utilisée. Au lieu de cela, le RunLoop peut être tourné par un setTimeout
événement (voir la fonction invokeLaterTimers
), qui est suffisamment intelligent pour parcourir tous les temporisateurs existants, déclencher tous ceux qui ont expiré, déterminer le premier temporisateur futur et définir un setTimeout
pour cet événement uniquement, qui fera tourner à nouveau le RunLoop lorsqu'il se déclenchera. Cette approche est plus efficace que de demander à chaque temporisateur d'appeler setTimeout et de se réveiller, car dans ce cas, un seul appel setTimeout doit être effectué, et le RunLoop est suffisamment intelligent pour déclencher tous les différents chronomètres qui pourraient se déclencher en même temps.
sync
Voici un extrait de la boucle d'exécution, au milieu d'une boucle à travers toutes les files d'attente de la boucle d'exécution. Notez le cas particulier de la file d'attente sync
: parce que sync
est une file d'attente particulièrement volatile, dans laquelle les données sont propagées dans toutes les directions, Ember.beginPropertyChanges()
est appelé pour empêcher tout observateur de être renvoyé, suivi d'un appel à Ember.endPropertyChanges
. C'est sage: si au cours du vidage de la file d'attente sync
, il est tout à fait possible qu'une propriété sur un objet change plusieurs fois avant de se reposer sur sa valeur finale, et vous ne voudriez pas gaspiller des ressources immédiatement licencier des observateurs à chaque changement.
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);
}
J'espère que cela t'aides. J'ai certainement dû apprendre un peu juste pour écrire cette chose, ce qui était un peu le point.