Aggiornamento 10/9/2013: controlla questa visualizzazione interattiva del ciclo di esecuzione: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html
Aggiornamento 5/9/2013: tutti i concetti di base di seguito sono ancora aggiornati, ma a partire da questo commit , l'implementazione di Ember Run Loop è stata suddivisa in una libreria separata chiamata backburner.js , con alcune differenze API molto minori.
Prima di tutto, leggi questi:
http://blog.sproutcore.com/the-run-loop-part-1/
http://blog.sproutcore.com/the-run-loop-part-2/
Non sono accurati al 100% per Ember, ma i concetti fondamentali e la motivazione alla base di RunLoop si applicano ancora generalmente a Ember; solo alcuni dettagli di implementazione differiscono. Ma, passiamo alle tue domande:
Quando inizia Ember RunLoop. Dipende da Router, Visualizzazioni, Controller o qualcos'altro?
Tutti gli eventi utente di base (ad esempio eventi della tastiera, eventi del mouse, ecc.) Attiveranno il ciclo di esecuzione. Ciò garantisce che qualsiasi modifica apportata alle proprietà associate dall'evento acquisito (mouse / tastiera / timer / ecc.) Venga completamente propagata in tutto il sistema di associazione dati di Ember prima di restituire il controllo al sistema. Quindi, muovendo il mouse, premendo un tasto, facendo clic su un pulsante, ecc., Tutto avvia il ciclo di esecuzione.
quanto tempo ci vuole circa (so che è piuttosto sciocco chiedere e dipende da molte cose ma sto cercando un'idea generale, o forse se c'è un tempo minimo o massimo che un ciclo di esecuzione può impiegare)
In nessun momento RunLoop terrà traccia di quanto tempo impiega a propagare tutte le modifiche attraverso il sistema e quindi interromperà RunLoop dopo aver raggiunto un limite di tempo massimo; piuttosto, il RunLoop verrà sempre eseguito fino al completamento e non si fermerà fino a quando tutti i timer scaduti non saranno stati chiamati, i collegamenti propagati e forse i loro collegamenti propagati e così via. Ovviamente, più cambiamenti devono essere propagati da un singolo evento, più tempo impiegherà RunLoop a finire. Ecco un esempio (piuttosto ingiusto) di come RunLoop può impantanarsi con la propagazione delle modifiche rispetto a un altro framework (Backbone) che non ha un ciclo di esecuzione: http://jsfiddle.net/jashkenas/CGSd5/. Morale della storia: RunLoop è davvero veloce per la maggior parte delle cose che vorresti fare in Ember, ed è qui che risiede gran parte del potere di Ember, ma se ti ritrovi a voler animare 30 cerchi con Javascript a 60 fotogrammi al secondo, ecco potrebbero essere modi migliori per farlo piuttosto che affidarsi a RunLoop di Ember.
RunLoop viene eseguito sempre o indica solo un periodo di tempo dall'inizio alla fine dell'esecuzione e potrebbe non essere eseguito per un po 'di tempo.
Non viene eseguito sempre - deve restituire il controllo al sistema a un certo punto altrimenti la tua app si bloccherebbe - è diverso, ad esempio, da un ciclo di esecuzione su un server che ha un while(true)
e va avanti all'infinito finché il server riceve un segnale per spegnersi ... Ember RunLoop non ne ha, while(true)
ma viene avviato solo in risposta agli eventi utente / timer.
Se una vista viene creata dall'interno di un RunLoop, è garantito che tutto il suo contenuto entrerà nel DOM al termine del ciclo?
Vediamo se riusciamo a capirlo. Uno dei grandi cambiamenti da SC a Ember RunLoop è che, invece di scorrere avanti e indietro tra invokeOnce
e invokeLast
(che vedi nel diagramma nel primo collegamento sull'RL di SproutCore), Ember ti fornisce un elenco di 'code' che, nel nel corso di un ciclo di esecuzione, è possibile programmare azioni (funzioni da chiamare durante il ciclo di esecuzione) specificando a quale coda appartiene l'azione (esempio dall'origine :) Ember.run.scheduleOnce('render', bindView, 'rerender');
.
Se si guarda run_loop.js
nel codice sorgente, si vede Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
, ma se si apre il debugger JavaScript nel browser in un app Ember e valutare Ember.run.queues
, si ottiene un elenco più completo di code: ["sync", "actions", "render", "afterRender", "destroy", "timers"]
. Ember mantiene la loro base di codice piuttosto modulare e consentono al tuo codice, così come al proprio codice in una parte separata della libreria, di inserire più code. In questo caso, la libreria Ember Views inserisce render
e afterRender
accoda, in particolare dopo la actions
coda. Arriverò al motivo per cui potrebbe essere in un secondo. Innanzitutto, l'algoritmo RunLoop:
L'algoritmo RunLoop è praticamente lo stesso descritto negli articoli del ciclo di esecuzione di SC sopra:
- Esegui il tuo codice tra RunLoop
.begin()
e .end()
, solo in Ember, vorrai invece eseguire il tuo codice all'interno Ember.run
, che chiamerà internamente begin
e end
per te. (Solo il codice del ciclo di esecuzione interno nella base di codice Ember utilizza ancora begin
e end
, quindi dovresti semplicemente attenersi a Ember.run
)
- Dopo
end()
essere stato chiamato, RunLoop si avvia per propagare ogni singola modifica apportata dal blocco di codice passato alla Ember.run
funzione. Ciò include la propagazione dei valori delle proprietà associate, il rendering delle modifiche della vista al DOM, ecc. Ecc. L'ordine in cui vengono eseguite queste azioni (associazione, rendering di elementi DOM, ecc.) È determinato Ember.run.queues
dall'array descritto sopra:
- Il ciclo di esecuzione inizierà dalla prima coda, che è
sync
. Eseguirà tutte le azioni che sono state pianificate nella sync
coda dal Ember.run
codice. Queste azioni possono anche programmare più azioni da eseguire durante questo stesso RunLoop, e spetta a RunLoop assicurarsi di eseguire ogni azione fino a quando tutte le code non vengono scaricate. In questo modo, alla fine di ogni coda, RunLoop esaminerà tutte le code precedentemente scaricate e verificherà se sono state pianificate nuove azioni. In tal caso, deve iniziare dall'inizio della prima coda con azioni pianificate non eseguite e svuotare la coda, continuando a tracciare i suoi passaggi e ricominciare quando necessario finché tutte le code non sono completamente vuote.
Questa è l'essenza dell'algoritmo. È così che i dati associati vengono propagati attraverso l'app. Ci si può aspettare che una volta che un RunLoop viene eseguito fino al completamento, tutti i dati associati saranno completamente propagati. Allora, per quanto riguarda gli elementi DOM?
L'ordine delle code, comprese quelle aggiunte dalla libreria Ember Views, è importante qui. Notalo render
e afterRender
vieni dopo sync
, e action
. La sync
coda contiene tutte le azioni per la propagazione dei dati associati. ( action
, dopo di che, è usato solo scarsamente nella fonte Ember). In base all'algoritmo di cui sopra, è garantito che nel momento in cui RunLoop arriva alla render
coda, tutte le associazioni di dati avranno terminato la sincronizzazione. Ciò è di progettazione: non si vuole a svolgere il compito di rendere costoso elementi DOM primasincronizzazione dei data-bindings, dal momento che ciò richiederebbe probabilmente il riesame degli elementi DOM con dati aggiornati - ovviamente un modo molto inefficiente e soggetto a errori per svuotare tutte le code di RunLoop. Quindi Ember esplora in modo intelligente tutto il lavoro di associazione dei dati possibile prima di rendere gli elementi DOM nella render
coda.
Quindi, finalmente, per rispondere alla tua domanda, sì, puoi aspettarti che tutti i rendering DOM necessari avranno avuto luogo entro la fine del tempo Ember.run
. Ecco un jsFiddle da dimostrare: http://jsfiddle.net/machty/6p6XJ/328/
Altre cose da sapere su RunLoop
Osservatori vs binding
È importante notare che Observers e Bindings, pur avendo la funzionalità simile di rispondere ai cambiamenti in una proprietà "controllata", si comportano in modo totalmente diverso nel contesto di un RunLoop. La propagazione del binding, come abbiamo visto, viene pianificata nella sync
coda per essere eventualmente eseguita da RunLoop. Gli osservatori, d'altra parte, si attivano immediatamente quando la proprietà osservata cambia senza dover prima essere programmati in una coda RunLoop. Se un Observer e un binding "osservano" tutti la stessa proprietà, l'osservatore verrà sempre chiamato il 100% delle volte prima che il binding venga aggiornato.
scheduleOnce
e Ember.run.once
Uno dei grandi aumenti di efficienza nei modelli di aggiornamento automatico di Ember si basa sul fatto che, grazie a RunLoop, più azioni RunLoop identiche possono essere unite ("rimbalzate", se vuoi) in un'unica azione. Se guardi negli run_loop.js
interni, vedrai che le funzioni che facilitano questo comportamento sono le funzioni correlate scheduleOnce
e Em.run.once
. La differenza tra loro non è così importante quanto sapere che esistono e come possono scartare le azioni duplicate in coda per evitare calcoli eccessivi e dispendiosi durante il ciclo di esecuzione.
E i timer?
Anche se "timer" è una delle code predefinite elencate sopra, Ember fa riferimento alla coda solo nei casi di test RunLoop. Sembra che una tale coda sarebbe stata utilizzata nei giorni di SproutCore sulla base di alcune delle descrizioni degli articoli sopra sui timer come l'ultima cosa da sparare. In Ember, la timers
coda non viene utilizzata. Invece, il RunLoop può essere attivato da un setTimeout
evento gestito internamente (vedere la invokeLaterTimers
funzione), che è abbastanza intelligente da scorrere tutti i timer esistenti, attivare tutti quelli che sono scaduti, determinare il primo timer futuro e impostare un timer internosetTimeout
solo per quell'evento, che farà girare di nuovo RunLoop quando si attiva. Questo approccio è più efficiente che avere ogni chiamata con timer setTimeout e svegliarsi, poiché in questo caso è necessario effettuare una sola chiamata setTimeout e RunLoop è abbastanza intelligente da attivare tutti i diversi timer che potrebbero attivarsi contemporaneamente tempo.
Ulteriore rimbalzo con la sync
coda
Ecco un frammento del ciclo di esecuzione, nel mezzo di un ciclo attraverso tutte le code del ciclo di esecuzione. Si noti il caso speciale della sync
coda: poiché sync
è una coda particolarmente volatile, in cui i dati vengono propagati in ogni direzione, Ember.beginPropertyChanges()
viene chiamata per evitare che eventuali osservatori vengano attivati, seguita da una chiamata a Ember.endPropertyChanges
. Questo è saggio: se durante lo svuotamento della sync
coda, è del tutto possibile che una proprietà su un oggetto cambi più volte prima di riposare sul suo valore finale, e non vorrai sprecare risorse sparando immediatamente osservatori per ogni singola modifica .
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);
}
Spero che questo ti aiuti. Sicuramente ho dovuto imparare un bel po 'solo per scrivere questa cosa, il che era il punto.