Оновлення 9.10.2013: Ознайомтеся з цією інтерактивною візуалізацією циклу запуску: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html
Оновлення 9.5.2013: усі основні концепції, наведені нижче, все ще актуальні, але станом на цей коміт реалізація Ember Run Loop була розділена на окрему бібліотеку під назвою backburner.js , з деякими дуже незначними відмінностями в API.
Спочатку прочитайте:
http://blog.sproutcore.com/the-run-loop-part-1/
http://blog.sproutcore.com/the-run-loop-part-2/
Вони не на 100% точні до Ембер, але основні концепції та мотивація, що стоять за RunLoop, як правило, стосуються Ембер; лише деякі деталі реалізації відрізняються. Але, щодо ваших запитань:
Коли починається Ember RunLoop. Це залежить від маршрутизатора, подання, контролерів чи чогось іншого?
Усі основні події користувача (наприклад, події на клавіатурі, події миші тощо) запускають цикл запуску. Це гарантує, що будь-які зміни, внесені до властивостей, пов’язаних захопленою подією (миша / клавіатура / таймер / тощо), повністю розповсюджуються по всій системі прив’язки даних Ember перед тим, як повернути контроль назад до системи. Отже, рухаючи мишкою, натискаючи клавішу, клацаючи кнопку тощо, все запускає цикл запуску.
скільки часу це приблизно займає (я знаю, що це досить безглуздо запитувати і залежати від багатьох речей, але я шукаю загальну ідею, або, можливо, якщо пробіг може зайняти мінімальний або максимальний час)
Жоден момент RunLoop ніколи не буде відстежувати, скільки часу потрібно для розповсюдження всіх змін через систему, а потім зупинить RunLoop після досягнення максимального обмеження часу; швидше, RunLoop завжди буде виконуватися до кінця і не зупинятиметься доти, доки не буде викликано всі таймери, що минули, не буде розповсюджено прив’язки і, можливо, їх прив’язки тощо. Очевидно, що чим більше змін потрібно розповсюдити з однієї події, тим довше буде виконуватися RunLoop. Ось (досить несправедливий) приклад того, як RunLoop може заглибитись із поширенням змін порівняно з іншим фреймворком (Backbone), який не має циклу запуску: http://jsfiddle.net/jashkenas/CGSd5/. Мораль історії: RunLoop дійсно швидкий для більшості речей, які ви коли-небудь хотіли б зробити в Ember, і в цьому полягає велика частина потужності Ember, але якщо ви виявите бажання анімувати 30 кіл за допомогою Javascript зі швидкістю 60 кадрів в секунду, може бути кращими способами це зробити, ніж покладатися на RunLoop від Ember.
Чи виконується RunLoop постійно або це просто вказівка періоду часу від початку до кінця виконання та може не працювати протягом деякого часу.
Він не виконується в будь-який час - він повинен повернути управління в систему в якийсь момент, інакше ваш додаток зависне - він відрізняється від, скажімо, циклу запуску на сервері, який має while(true)
і триває нескінченно сервер отримує сигнал до вимкнення ... Ember RunLoop такого не має, while(true)
але закручується лише у відповідь на події користувача / таймера.
Якщо представлення створюється з одного RunLoop, чи гарантується, що весь його вміст перетворить його в DOM до моменту закінчення циклу?
Давайте подивимось, чи зможемо ми це зрозуміти. Одне з найбільших змін від SC до Ember RunLoop полягає в тому, що замість того, щоб циклічно переходити між собою invokeOnce
і invokeLast
(що ви бачите на схемі у першому посиланні про RL SproutCore), Ember надає вам список "черг", які в В ході циклу запуску можна запланувати дії (функції, які потрібно викликати під час циклу запуску), вказавши, до якої черги належить дія (наприклад, з джерела:) Ember.run.scheduleOnce('render', bindView, 'rerender');
.
Якщо ви подивіться на run_loop.js
в вихідному коді, ви бачите Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
, але якщо ви відкриєте відладчик JavaScript в браузері в додатку Ember і оцінити Ember.run.queues
, ви отримаєте більш повний список черг: ["sync", "actions", "render", "afterRender", "destroy", "timers"]
. Ember зберігає свою модульну базу даних досить модульною, і вони дозволяють вашому коду, а також його власному коду в окремій частині бібліотеки вставляти більше черг. У цьому випадку бібліотека Ember Views вставляє render
та afterRender
черги, зокрема після actions
черги. Я довідаюся, чому це може бути через секунду. По-перше, алгоритм RunLoop:
Алгоритм RunLoop майже такий самий, як описано в статтях циклу запуску SC вище:
- Ви запускаєте свій код між RunLoop
.begin()
і .end()
тільки в Ember ви хочете , щоб замість запуску коду всередині Ember.run
, який буде внутрішньо зателефонувати begin
і end
для вас. (Тільки внутрішній код циклу запуску в базі коду Ember все ще використовує begin
і end
, тому вам слід просто дотримуватися Ember.run
)
- Після
end()
виклику RunLoop потім запускає передачу для розповсюдження кожної зміни, зробленої фрагментом коду, переданого Ember.run
функції. Це включає розповсюдження значень прив'язаних властивостей, візуалізацію змін подання до DOM тощо, тощо. Порядок, в якому виконуються ці дії (прив'язка, візуалізація елементів DOM тощо), визначається Ember.run.queues
описаним вище масивом:
- Цикл запуску запуститься в першій черзі, яка є
sync
. Він запустить усі дії, які були заплановані в sync
чергу за Ember.run
кодом. Ці дії також можуть самі запланувати більше дій, які слід виконати під час того самого RunLoop, і RunLoop повинен забезпечити, щоб він виконував кожну дію, доки всі черги не будуть очищені. Це робиться так, що наприкінці кожної черги RunLoop переглядає всі попередньо очищені черги та перевіряє, чи не було заплановано нових дій. Якщо так, то він повинен починатись на початку самої ранньої черги з невиконаними запланованими діями та змивати чергу, продовжуючи відстежувати її кроки та починати спочатку, доки всі черги повністю не порожні.
У цьому суть алгоритму. Ось як пов’язані дані поширюються через додаток. Ви можете очікувати, що як тільки RunLoop запуститься до кінця, всі пов'язані дані будуть повністю розповсюджені. Отже, як щодо елементів DOM?
Тут важливий порядок черг, включаючи ті, що додаються бібліотекою Ember Views. Зауважте, що render
і afterRender
приходьте після sync
, і action
. sync
Черга містить всі дії для поширення пов'язаних даних. ( action
після цього лише рідко використовується в джерелі Ембер). На основі вищезазначеного алгоритму гарантується, що до моменту, коли RunLoop потрапить до render
черги, усі прив'язки даних завершать синхронізацію. Це дизайн: ви не хочете , щоб виконати завдання дорогу рендеринга DOM елементів , перш ніжсинхронізація прив’язок даних, оскільки для цього, ймовірно, знадобиться повторний рендерінг елементів DOM з оновленими даними - очевидно, дуже неефективний і схильний до помилок спосіб очищення всіх черг RunLoop. Тож Ember розумно перевіряє всю роботу з прив’язки даних, яку може, перед тим, як відображати елементи DOM у render
черзі.
Отже, нарешті, щоб відповісти на ваше запитання, так, ви можете очікувати, що будь-які необхідні візуалізації DOM відбудуться до моменту Ember.run
закінчення. Ось jsFiddle для демонстрації: http://jsfiddle.net/machty/6p6XJ/328/
Інші речі, які слід знати про RunLoop
Спостерігачі проти прив’язок
Важливо зазначити, що спостерігачі та прив'язки, маючи подібну функціональність реагування на зміни у властивості "спостерігається", поводяться абсолютно по-різному в контексті RunLoop. Як ми вже бачили, розповсюдження прив’язки планується до sync
черги, щоб врешті-решт виконати його RunLoop. З іншого боку, спостерігачі запускають негайно, коли спостерігається властивість змінюється, без необхідності попереднього планування в чергу RunLoop. Якщо спостерігач і прив'язка "спостерігають" за одним і тим же властивістю, спостерігач завжди буде викликаний на 100% раніше, ніж прив'язка буде оновлена.
scheduleOnce
і Ember.run.once
Одне з найбільших підвищення ефективності в шаблонах автоматичного оновлення Ember базується на тому, що завдяки RunLoop кілька однакових дій RunLoop можна об'єднати ("зняти", якщо хочете) в одну дію. Якщо ви подивіться в run_loop.js
нутрощі, ви побачите , що функції , які полегшують таку поведінку є відповідними функціями scheduleOnce
і Em.run.once
. Різниця між ними не настільки важлива, як те, що вони знають, і те, як вони можуть відкидати дублікати дій у черзі, щоб запобігти великій кількості роздутих, марнотратних розрахунків під час циклу запуску.
А як щодо таймерів?
Незважаючи на те, що "таймери" є однією з перелічених вище черг за замовчуванням, Ember робить посилання на чергу лише у своїх тестових випадках RunLoop. Здається, така черга використовувалася б у дні SproutCore на основі деяких описів із вищезазначених статей про те, що таймери були останніми, що спрацьовували. В Ембер timers
черга не використовується. Натомість RunLoop може бути запущений внутрішньо керованою setTimeout
подією (див. invokeLaterTimers
Функцію), яка є досить розумною, щоб прокрутити всі існуючі таймери, запустити всі, що закінчились, визначити найраніший майбутній таймер та встановити внутрішнійsetTimeout
лише для цієї події, яка знову запустить RunLoop, коли вона спрацює. Цей підхід є більш ефективним, ніж кожен виклик таймера setTimeout і пробудження, оскільки в цьому випадку потрібно зробити лише один виклик setTimeout, а RunLoop досить розумний, щоб запускати всі різні таймери, які можуть одночасно спрацьовувати час.
Подальше зняття з sync
черги
Ось фрагмент циклу циклу, посередині циклу через усі черги циклу циклу. Зверніть увагу на особливий випадок sync
черги: оскільки sync
це особливо нестабільна черга, в якій дані поширюються в будь-якому напрямку, Ember.beginPropertyChanges()
викликається, щоб запобігти звільненню будь-яких спостерігачів, за яким слід виклик Ember.endPropertyChanges
. Це розумно: якщо під час очищення sync
черги цілком можливо, що властивість об’єкта зміниться кілька разів, перш ніж зупинитися на його кінцевій вартості, і ви не хотіли б витрачати ресурси, негайно звільняючи спостерігачів за кожну окрему зміну .
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);
}
Сподіваюся, це допомагає. Мені, безумовно, довелося навчитися зовсім небагато, щоб просто написати цю річ, що було якоюсь суттю.