Próbuję zrozumieć, jak działa Ember RunLoop i co sprawia, że działa. Przejrzałem dokumentację , ale wciąż mam wiele pytań na jej temat. Jestem zainteresowany lepszym zrozumieniem, jak działa RunLoop, aby móc wybrać odpowiednią metodę w jego przestrzeni nazw, gdy muszę odłożyć wykonanie kodu na później.
- Kiedy uruchamia się Ember RunLoop. Czy zależy to od routera, widoków, kontrolerów czy czegoś innego?
- 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)
- 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.
- 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?
Wybacz mi, jeśli są to bardzo podstawowe pytania, myślę, że zrozumienie ich pomoże takim noobom jak ja lepiej używać Ember.
Odpowiedzi:
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
iinvokeLast
(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, widziszEmber.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 wstawiarender
iafterRender
kolejkuje, szczególnie poactions
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:
.begin()
i.end()
tylko w Ember będziesz chciał zamiast tego uruchomić swój kod w ramachEmber.run
, który będzie wewnętrznie wywoływałbegin
iend
dla Ciebie. (Tylko wewnętrzny kod pętli uruchamiania w bazie kodu Ember nadal używabegin
iend
, więc powinieneś po prostu trzymać sięEmber.run
)end()
wywołaniu RunLoop uruchamia się, aby propagować każdą zmianę wprowadzoną przez fragment kodu przekazany doEmber.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 przezEmber.run.queues
tablicę opisaną powyżej:sync
. Uruchomi wszystkie akcje, które zostały zaplanowane wsync
kolejce przezEmber.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
iafterRender
przyjdź późniejsync
, iaction
.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 dorender
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 wrender
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
iEmber.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 funkcjescheduleOnce
iEm.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 przezsetTimeout
zdarzenie zarządzane wewnętrznie (patrzinvokeLaterTimers
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
kolejkiOto 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łanieEmber.endPropertyChanges
. Jest to mądre: jeśli w trakcie opróżnianiasync
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 .Mam nadzieję że to pomoże. Zdecydowanie musiałem się sporo nauczyć, żeby to napisać, co było w pewnym sensie.
źródło