Примечание редактора: эта статья была воспроизведена еженедельником Qiwu Weekly, авторизованным публичным аккаунтом Дабаозашу Lv Dabao. Приходите учиться вместе!
В vue есть специальный API, nextTick. Согласно объяснению официальной документации, он может выполнять обратный вызов после обновления DOM.Использование выглядит следующим образом:
// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})
Хотя фреймворк MVVM не рекомендует обращаться к DOM, иногда такая необходимость возникает, особенно при работе со сторонними плагинами, манипуляции с DOM неизбежны. И nextTick обеспечивает мост, чтобы убедиться, что мы работаем с обновленным DOM.
Эта статья начинается с вопроса: как Vue определяет, что DOM был обновлен?
Получите свою собственную базу знаний внешнего интерфейса, единственным API, который может отслеживать изменения DOM, является MutationObserver, далее именуемый MO.
Понимание MutationObserver
MutationObserver — это новый атрибут в HTML5. Он используется для отслеживания событий модификации DOM. Он может отслеживать изменения атрибутов узла, текстового содержимого, дочерних узлов и т. д. Это мощный инструмент. Основное применение:
//MO基本用法
var observer = new MutationObserver(function(){
//这里是回调函数
console.log('DOM被修改了!');
});
var article = document.querySelector('article');
observer.observer(article);
Использование МО не является предметом этой статьи. Здесь нам нужно подумать: использует ли Vue MO для отслеживания завершения обновления DOM?
Затем откройте исходный код vue и посмотрите, в том месте, где реализован nextTick, действительно можно увидеть такой код:
//vue@2.2.5 /src/core/util/env.js
if (typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]')) {
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
}
Чтобы кратко объяснить, если обнаруживается, что браузер поддерживает MO, создается текстовый узел и отслеживается событие изменения этого текстового узла, чтобы инициировать выполнение nextTickHandler (то есть обратный вызов обновления DOM). В следующем коде будет выполнена ручная модификация свойств текстового узла, чтобы можно было ввести функцию обратного вызова.
После общего взгляда кажется, что я могу получить настоящий молот: О! Vue использует MutationObserver для мониторинга обновлений DOM!
Вы не чувствуете, что что-то не так? Давайте задумаемся об этом на секунду:
-
Что мы хотим отслеживать, так это то, что DOM в шаблоне обновляется.Почему Vue создает текстовый узел для его мониторинга?Это немного неразумно!
-
Может быть, если текстовый узел, созданный вами, обновляется, может ли он представлять собой обновление других узлов DOM? Какой в этом смысл!
Кажется, что выводы, которые мы сделали выше, неверны.На данный момент нам нужно поговорить о механизме событийного цикла js.
Цикл событий
В среде запуска js давайте просто поговорим здесь о браузере, который обычно сопровождается множеством событий, таких как клики пользователя, отрисовка страницы, выполнение скрипта, сетевые запросы и так далее. Для координации обработки этих событий браузеры используют механизм цикла обработки событий.
Вкратце, цикл событий поддерживает одну или несколько очередей задач, а вышеупомянутые события используются в качестве источников задач для добавления задач в очереди. Для обработки этих задач существует непрерывный поток, и каждый раз, когда он выполняется, он удаляется из очереди, что представляет собой цикл обработки событий, как показано на следующем рисунке:
Обычно мы используем setTimeout для выполнения асинхронного кода, фактически мы добавляем задачу в конец очереди задач и выполняем ее после выполнения предыдущих задач.
Здесь ключевой момент: в конце каждого цикла событий будет этап рендеринга пользовательского интерфейса, который заключается в обновлении DOM. Почему стандарт разработан таким образом? Рассмотрим следующий код:
for(let i=0; i<100; i++){
dom.style.left = i + 'px';
}
Делают ли браузеры 100 обновлений DOM? Очевидно, что нет, это слишком требовательно к производительности. На самом деле эти 100 циклов for относятся к одной и той же задаче, и браузер выполняет только одно обновление DOM после выполнения задачи.
Затем приходит наша идея: пока код в nextTick выполняется после этапа рендеринга пользовательского интерфейса, нельзя ли получить доступ к обновленному DOM?
Vue — это такой способ мышления: он не использует MO для мониторинга изменений DOM, а использует управление очередью для достижения цели. Так как же Vue обеспечивает управление очередью? Естественно, мы можем думать о setTimeout и поместить код, который будет выполняться nextTick, в качестве следующей задачи и поместить его в конец очереди.
Однако все не так просто, процесс ответа данных Vue включает в себя: изменение данных -> уведомление Watcher -> обновление DOM. Изменения данных не находятся под нашим контролем и могут произойти в любое время. Если это произойдет до перерисовки, произойдет несколько рендеров. Это означает потерю производительности, которую Vue не хочет видеть.
Поэтому управление очередью Vue хорошо продумано (и претерпело множество изменений). Перед этим нам нужно понять еще одну важную концепцию цикла событий — микрозадачи.
microtask
По названию мы можем назвать это микрозадачами. Соответственно задачи в очереди задач также называются макрозадачами. Названия похожи, но свойства разные.
Каждый цикл событий содержит очередь микрозадач, после окончания цикла микрозадачи в очереди будут выполнены и удалены, после чего запустится следующий цикл событий.
Микрозадачи, которые добавляются в очередь микрозадач после выполнения микрозадачи, также будут выполняться до следующего цикла обработки событий. То есть макрозадача может быть выполнена только после выполнения всех микрозадач, а микрозадача имеет более высокий приоритет.
Эта функция микрозадач — просто лучший выбор для управления очередью! Vue также вызывает nextTick для внутреннего асинхронного управления очередью для обновления DOM. Когда мы сами вызываем nextTick, он добавляет нашу собственную функцию обратного вызова к микрозадаче, которая обновляет DOM, тем самым гарантируя выполнение нашего кода после обновления DOM и избегая возможных проблем с множественным выполнением setTimeout.
Общие микрозадачи: Promise, MutationObserver, Object.observe (заброшен) и process.nextTick в nodejs.
Хм? Кажется, я видел MutationObserver.Значит ли это, что Vue использует MO для использования своей функции микрозадач, а не для мониторинга DOM? Да, это оно. Ядро микрозадачи, вы можете использовать МО или нет. Фактически, Vue удалил код, связанный с MO, в версии 2.5, потому что это новая функция HTML5, а в iOS все еще есть ошибки.
Тогда оптимальной стратегией микрозадач является Promise, и смущает то, что Promise — это новое дополнение к ES6, а также есть проблемы с совместимостью. Таким образом, Vue сталкивается со стратегией понижения.
Стратегия понижения Vue
Как мы упоминали выше, лучшим выбором для управления очередью является микрозадача, а лучшим выбором для микрозадачи является Promise.Но если текущая среда не поддерживает Promise, vue должен быть понижен до макрозадачи для управления очередью.
Какие есть варианты макрозадач? Ранее упоминалось, что setTimeout — это одно, но это не идеальное решение. Поскольку минимальный временной интервал для выполнения setTimeout составляет около 4 мс, возникает небольшая задержка. Есть ли другие варианты?
Не продавайте это В исходном коде vue2.5 схема понижения макрозадачи такова: setImmediate, MessageChannel, setTimeout.
setImmediate — самое идеальное решение, но, к сожалению, его поддерживают только IE и nodejs.
Обратный вызов onmessage MessageChannel — это тоже микрозадача, но это также новый API, сталкивающийся с затруднениями совместимости...
Таким образом, окончательным решением является setTimeout.Хотя у него есть задержка выполнения, это может привести к многократному рендерингу, что не составляет труда.
Суммировать
Вышеизложенный принцип реализации метода nextTick vue.Подводя итог:
-
Vue использует асинхронную очередь для управления обновлением DOM и обратным вызовом nextTick для последовательного выполнения.
-
Благодаря высокому приоритету микрозадачи могут гарантировать, что микрозадачи в очереди будут выполнены до цикла обработки событий.
-
Из-за проблем с совместимостью vue пришлось выполнить план перехода с микрозадачи на макрозадачу.
Релевантная информация:
стандарт цикла событий
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
Запись изменения nextTick vue2.5
https://github.com/vuejs/vue/commit/6e41679a96582da3e0a60bdbf123c33ba0e86b31
Статья об анализе исходного кода
https://github.com/answershuto/learnVue/blob/master/docs/Vue.js%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0DOM%E7%AD%96%E7%95%A5%E5%8F%8AnextTick.MarkDown
О Еженедельнике Циву
«Qiwu Weekly» — это сообщество передовых технологий, управляемое профессиональной командой «Qiwu Troupe» компании 360. Подписавшись на официальный аккаунт, вы можете отправить нам статью, отправив ссылку прямо на фон.