Aktualizacja 09.10.2013: Sprawdź tę interaktywną wizualizację pętli uruchamiania: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html
Aktualizacja 9.05.2013: wszystkie podstawowe koncepcje poniżej są nadal aktualne, ale od tego zatwierdzenia implementacja Ember Run Loop została podzielona na oddzielną bibliotekę o nazwie backburner.js , z pewnymi bardzo niewielkimi różnicami w API.
Po pierwsze, przeczytaj te:
http://blog.sproutcore.com/the-run-loop-part-1/
http://blog.sproutcore.com/the-run-loop-part-2/
Nie są w 100% dokładne w stosunku do Ember, ale podstawowe koncepcje i motywacja RunLoop nadal generalnie odnoszą się do Ember; różnią się tylko niektóre szczegóły implementacji. Ale jeśli chodzi o twoje pytania:
Kiedy uruchamia się Ember RunLoop. Czy zależy to od routera, widoków, kontrolerów czy czegoś innego?
Wszystkie podstawowe zdarzenia użytkownika (np. Zdarzenia klawiatury, zdarzenia myszy itp.) Uruchomią pętlę uruchamiania. Gwarantuje to, że wszelkie zmiany wprowadzone w powiązanych właściwościach przez przechwycone zdarzenie (mysz / klawiatura / zegar / itp.) Są w pełni propagowane w systemie powiązań danych Ember przed zwróceniem sterowania z powrotem do systemu. Tak więc poruszanie myszą, naciśnięcie klawisza, kliknięcie przycisku itp. Uruchamia pętlę uruchamiania.
jak długo to w przybliżeniu trwa (wiem, że jest to raczej głupie pytanie i zależne od wielu rzeczy, ale szukam ogólnego pomysłu, a może może jest minimalny lub maksymalny czas, jaki może zająć runloop)
W żadnym momencie RunLoop nigdy nie będzie śledzić, ile czasu zajmuje propagowanie wszystkich zmian w systemie, a następnie zatrzyma RunLoop po osiągnięciu maksymalnego limitu czasu; raczej RunLoop będzie zawsze działał do końca i nie zatrzyma się, dopóki nie zostaną wywołane wszystkie wygasłe timery, rozpropagowane powiązania i być może ich powiązania, i tak dalej. Oczywiście im więcej zmian trzeba propagować z jednego zdarzenia, tym dłużej RunLoop potrwa do zakończenia. Oto (dość niesprawiedliwy) przykład tego, jak RunLoop może ugrzęznąć w propagowaniu zmian w porównaniu z innym frameworkiem (Backbone), który nie ma pętli uruchamiania: http://jsfiddle.net/jashkenas/CGSd5/. Morał z tej historii: RunLoop jest naprawdę szybki dla większości rzeczy, które kiedykolwiek chciałbyś robić w Ember, i tam leży duża moc Embera, ale jeśli chcesz animować 30 kręgów za pomocą JavaScript przy 60 klatkach na sekundę, tam może być lepszym sposobem rozwiązania tego problemu niż poleganie na RunLoop firmy Ember.
Czy RunLoop jest wykonywany przez cały czas, czy tylko wskazuje okres od początku do końca wykonywania i może nie działać przez jakiś czas.
Nie jest wykonywany przez cały czas - musi w pewnym momencie zwrócić kontrolę z powrotem do systemu, w przeciwnym razie aplikacja zawiesiłaby się - różni się od, powiedzmy, pętli uruchamiania na serwerze, który ma while(true)
i działa w nieskończoność do serwer otrzymuje sygnał do wyłączenia ... Ember RunLoop nie ma takiego, while(true)
ale jest uruchamiany tylko w odpowiedzi na zdarzenia użytkownika / timera.
Jeśli widok jest tworzony z poziomu jednego RunLoop, czy gwarantuje się, że cała jego zawartość trafi do DOM przed zakończeniem pętli?
Zobaczmy, czy uda nam się to rozgryźć. Jedną z dużych zmian z SC do Ember RunLoop jest to, że zamiast zapętlania się w tę iz powrotem pomiędzy invokeOnce
i invokeLast
(co widać na schemacie w pierwszym linku dotyczącym RL SproutCore), Ember zapewnia listę `` kolejek '', które w przebieg pętli uruchamiania można zaplanować akcje (funkcje, które mają być wywoływane podczas pętli uruchamiania), określając, do której kolejki należy akcja (przykład ze źródła:) Ember.run.scheduleOnce('render', bindView, 'rerender');
.
Jeśli spojrzeć run_loop.js
w kodzie źródłowym, widzisz Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
, jednak po otwarciu debugger JavaScript w przeglądarce w aplikacji Ember i ocenić Ember.run.queues
, masz pełniejszą listę kolejek: ["sync", "actions", "render", "afterRender", "destroy", "timers"]
. Ember utrzymuje swoją bazę kodów dość modularną i umożliwia wstawianie większej liczby kolejek przez Twój kod, a także jego własny kod w oddzielnej części biblioteki. W tym przypadku biblioteka Ember Views wstawia render
i afterRender
kolejkuje, szczególnie po actions
kolejce. Za chwilę wyjaśnię, dlaczego może to nastąpić. Najpierw algorytm RunLoop:
Algorytm RunLoop jest prawie taki sam, jak opisano w artykułach o pętli uruchamiania SC powyżej:
- Uruchamiasz swój kod między RunLoop
.begin()
i .end()
tylko w Ember będziesz chciał zamiast tego uruchomić swój kod w ramach Ember.run
, który będzie wewnętrznie wywoływał begin
i end
dla Ciebie. (Tylko wewnętrzny kod pętli uruchamiania w bazie kodu Ember nadal używa begin
i end
, więc powinieneś po prostu trzymać się Ember.run
)
- Po
end()
wywołaniu RunLoop uruchamia się, aby propagować każdą zmianę wprowadzoną przez fragment kodu przekazany do Ember.run
funkcji. Obejmuje to propagowanie wartości powiązanych właściwości, renderowanie zmian widoku w modelu DOM itp. Kolejność wykonywania tych czynności (wiązanie, renderowanie elementów DOM itp.) Jest określana przez Ember.run.queues
tablicę opisaną powyżej:
- Pętla uruchamiania rozpocznie się w pierwszej kolejce, czyli
sync
. Uruchomi wszystkie akcje, które zostały zaplanowane w sync
kolejce przez Ember.run
kod. Te akcje mogą również same zaplanować więcej akcji do wykonania podczas tego samego RunLoop, a RunLoop musi upewnić się, że wykona każdą akcję, dopóki wszystkie kolejki nie zostaną opróżnione. Sposób, w jaki to robi, polega na tym, że na końcu każdej kolejki RunLoop przejrzy wszystkie poprzednio opróżnione kolejki i sprawdzi, czy zostały zaplanowane jakieś nowe akcje. Jeśli tak, musi rozpocząć się na początku najwcześniejszej kolejki z niewykonanymi zaplanowanymi działaniami i opróżnić kolejkę, kontynuując śledzenie jej kroków i rozpoczynając od nowa, gdy jest to konieczne, aż wszystkie kolejki zostaną całkowicie opróżnione.
To jest istota algorytmu. W ten sposób powiązane dane są propagowane przez aplikację. Możesz spodziewać się, że po zakończeniu RunLoop wszystkie powiązane dane zostaną w pełni propagowane. A co z elementami DOM?
Ważna jest tutaj kolejność kolejek, w tym dodanych przez bibliotekę Ember Views. Zauważ to render
i afterRender
przyjdź później sync
, i action
. sync
Kolejka zawiera wszystkie działania na rzecz propagowania dane związane. ( action
po tym jest tylko rzadko używany w źródle Ember). Opierając się na powyższym algorytmie, gwarantuje się, że zanim RunLoop dotrze do render
kolejki, wszystkie powiązania danych zostaną zsynchronizowane. Jest to zgodne z projektem: nie chcesz, aby wykonać zadanie z drogiego renderowania elementów DOM przedsynchronizowanie powiązań danych, ponieważ prawdopodobnie wymagałoby to ponownego renderowania elementów DOM ze zaktualizowanymi danymi - oczywiście bardzo nieefektywny i podatny na błędy sposób opróżniania wszystkich kolejek RunLoop. Dzięki temu Ember w inteligentny sposób przegląda wszystkie możliwe prace związane z wiązaniem danych przed renderowaniem elementów DOM w render
kolejce.
A więc, na koniec, odpowiadając na twoje pytanie, tak, możesz spodziewać się, że wszelkie niezbędne renderingi DOM zostaną wykonane przed upływem czasu Ember.run
. Oto jsFiddle do zademonstrowania: http://jsfiddle.net/machty/6p6XJ/328/
Inne rzeczy, które warto wiedzieć o RunLoop
Obserwatorzy a wiązania
Należy zauważyć, że obserwatorzy i powiązania, mając podobną funkcjonalność reagowania na zmiany we właściwości „obserwowanej”, zachowują się zupełnie inaczej w kontekście RunLoop. Propagacja sync
powiązań , jak widzieliśmy, zostaje zaplanowana w kolejce, aby ostatecznie zostać wykonana przez RunLoop. Z drugiej strony obserwatorzy uruchamiają natychmiast po zmianie obserwowanej właściwości bez konieczności wcześniejszego planowania jej w kolejce RunLoop. Jeśli obserwator i powiązanie „obserwują” tę samą właściwość, obserwator będzie zawsze wywoływany przez 100% czasu wcześniej niż powiązanie zostanie zaktualizowane.
scheduleOnce
i Ember.run.once
Jeden z dużych wzrostów wydajności w szablonach automatycznej aktualizacji Ember jest oparty na fakcie, że dzięki RunLoop, wiele identycznych akcji RunLoop można połączyć („zdemontować”, jeśli wolisz) w jedną akcję. Jeśli spojrzysz na run_loop.js
wewnętrzne elementy, zobaczysz funkcje, które ułatwiają to zachowanie, są powiązane funkcje scheduleOnce
i Em.run.once
. Różnica między nimi nie jest tak ważna, jak wiedza o ich istnieniu i sposób, w jaki mogą odrzucić zduplikowane akcje w kolejce, aby zapobiec wielu rozdętym, marnotrawnym obliczeniom podczas pętli uruchamiania.
A co z licznikami czasu?
Mimo że „timery” są jedną z domyślnych kolejek wymienionych powyżej, Ember odwołuje się do kolejki tylko w swoich przypadkach testowych RunLoop. Wydaje się, że taka kolejka byłaby używana w czasach SproutCore na podstawie niektórych opisów z powyższych artykułów dotyczących timerów jako ostatniej odpalanej rzeczy. W Ember timers
kolejka nie jest używana. Zamiast tego RunLoop może zostać uruchomiony przez setTimeout
zdarzenie zarządzane wewnętrznie (patrz invokeLaterTimers
funkcja), które jest wystarczająco inteligentne, aby przejść przez wszystkie istniejące timery, uruchomić wszystkie wygasłe, określić najwcześniejszy przyszły timer i ustawić wewnętrznysetTimeout
tylko dla tego zdarzenia, co spowoduje ponowne uruchomienie RunLoop po uruchomieniu. Takie podejście jest bardziej wydajne niż ustawianie każdego wywołania timera setTimeout i wybudzanie się, ponieważ w tym przypadku wystarczy wykonać tylko jedno wywołanie setTimeout, a RunLoop jest wystarczająco inteligentny, aby uruchomić wszystkie różne timery, które mogą zadziałać w tym samym czasie czas.
Dalsze odbijanie się od sync
kolejki
Oto fragment z pętli uruchamiania, w środku pętli przez wszystkie kolejki w pętli uruchamiania. Zwróć uwagę na specjalny przypadek sync
kolejki: ponieważ sync
jest to szczególnie niestabilna kolejka, w której dane są propagowane we wszystkich kierunkach, Ember.beginPropertyChanges()
jest wywoływana, aby zapobiec wystrzeleniu jakichkolwiek obserwatorów, po których następuje wywołanie Ember.endPropertyChanges
. Jest to mądre: jeśli w trakcie opróżniania sync
kolejki jest całkowicie możliwe, że właściwość obiektu zmieni się wielokrotnie, zanim spocznie na jego ostatecznej wartości, a nie chciałbyś marnować zasobów, natychmiast zwalniając obserwatorów przy każdej pojedynczej zmianie .
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);
}
Mam nadzieję że to pomoże. Zdecydowanie musiałem się sporo nauczyć, żeby to napisać, co było w pewnym sensie.