Серия интерпретаций исходного кода Vue
- 1. Реактивный принцип Vue — понимание Observer, Dep, Watcher
- 2. VUE Реактивное принцип - как контролировать изменения массива
- 3. Принцип реактивности Vue — как следить за изменениями массива? Подробная версия
- 4. Асинхронное обновление Vue && анализ исходного кода nextTick
1. Асинхронная очередь обновлений Vue
(1) Асинхронное обновление Vue
Я верю, что все знают,VueОбновления представления на основе данных могут быть достигнуты.Например, мы просто пишем событие следующим образом:
methods: {
tap() {
for (let i = 0; i < 10; i++) {
this.a = i;
}
this.b = 666;
},
},
Когда мы запускаем это событие, представлениеaа такжеbОпределенные изменения будут обнаружены.
Итак, давайте подумаем об этом,VueКак управляется этот процесс изменений? Например, в приведенном выше случаеaповторяется 10 раз, тоVueБудет ли он отображать представление 10 раз? Очевидно, что нет, в конце концов, эта стоимость производительности очень велика. Ведь нам нужно толькоaПоследнее задание.
ФактическиVueПредставление обновляется асинхронно, а это значит, что оно будет ждать этогоtapПосле выполнения события проверка обнаруживает, что требуется только обновлениеaа такжеb, а затем обновите все сразу, чтобы избежать недопустимых обновлений.
VueОфициальные документы также подтверждают наши идеи, а именно:
Vue выполняется асинхронно при обновлении DOM. Как только он узнает об изменении данных, Vue откроет очередь и буферизует все изменения данных, которые происходят в том же цикле событий. Если один и тот же наблюдатель запускается несколько раз, он будет помещен в очередь только один раз. Эта дедупликация во время буферизации важна, чтобы избежать ненужных вычислений и манипуляций с DOM.
Подробнее см. вышеОфициальная документация Vue — очередь асинхронных обновлений.
(2) Асинхронные очереди при отправке обновлений
VueУведомить об обновлении просмотра, завершеноdep.notify, я думаю, вы должны были понять после прочтения этогоVueОтветный принцип. Тогда проверьте этоdep.notifyЧто вы наделали? Наберитесь терпения, вы приближаетесь к истине.
// dep.js
notify () {
const subs = this.subs.slice();
// 循环通知所有watcher更新
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
Первый цикл для уведомления всехwatcherОбновление, мы нашлиwatcherказненupdateметод.
// watcher.js
update () {
if (this.lazy) {
// 如果是计算属性
this.dirty = true
} else if (this.sync) {
// 如果要同步更新
this.run()
} else {
// 进入更新队列
queueWatcher(this)
}
}
updateМетод сначала оценивает, является ли это вычисляемым свойством или разработчик определяет синхронное обновление. Давайте сначала проигнорируем их, перейдем непосредственно к теме и введем метод асинхронной очереди.queueWatcher.
Тогда посмотри еще разqueueWatcher, я опустил большую часть кода.В конце концов код скучный.Чтобы облегчить всем понимание,это весь концептуальный код.
export function queueWatcher (watcher: Watcher) {
// 获取watcherid
const id = watcher.id
if (has[id] == null) {
// 保证只有一个watcher,避免重复
has[id] = true
// 推入等待执行的队列
queue.push(watcher)
// ...省略细节代码
}
// 将所有更新动作放入nextTick中,推入到异步队列
nextTick(flushSchedulerQueue)
}
function flushSchedulerQueue () {
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
watcher.run()
// ...省略细节代码
}
}
Из приведенного выше кода видно, что мы обновим всеwatcherставится очередьnextTickсередина.nextTickОфициальная интерпретация такова:
Выполняет отложенный обратный вызов после завершения следующего цикла обновления DOM. Используйте этот метод сразу после изменения данных, чтобы получить обновленную модель DOM.
Описание здесь на самом деле ограниченоnextTickнавыки, на самом делеnextTickэто асинхронный метод, возможно, такой же, как тот, который вы используетеsetTimeoutНе большая разница.
Тогда взглянитеnextTickЧто именно делает исходный код?
Во-вторых, анализ исходного кода nextTick
nextTickИсходного кода очень мало, и нужно перелистнуть всего несколько строк, но я не планирую его расширять, потому что читать код действительно скучно.
Код ниже состоит всего из нескольких строк, на самом деле вы можете пропустить его, чтобы увидеть заключение.
// timerFunc就是nextTick传进来的回调等... 细节不展开
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
// 当原生 Promise 不可用时,timerFunc 使用原生 MutationObserver
// MutationObserver不要在意它的功能,其实就是个可以达到微任务效果的备胎
)) {
timerFunc = () => {
// 使用 MutationObserver
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// 如果原生 setImmediate 可用,timerFunc 使用原生 setImmediate
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 最后的倔强,timerFunc 使用 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
РезюмеPromise > MutationObserver > setImmediate > setTimeout.
Конечно, иsetTimeoutНе большая разница~
Еще раз резюмируя приоритеты:microtask (jobs)приоритет.
nextTickисходный код почемуmicrotaskприоритет? Прежде чем мы поймем ответ на этот вопрос, мы должны рассмотретьeventLoopЗнание.
3. Цикл событий
(1) Очередь задач
Используйте 2 картинки, чтобы вызвать у вас краткую память, но я не буду вдаваться в подробности, вы можете найти информацию самостоятельно.
- Наши задачи синхронизации выполняются в основном потоке и формируют стек выполнения.
- Если вы столкнулись с асинхронными задачами, такими как
setTimeout,onClickДождитесь выполнения каких-то операций, результаты его выполнения мы поставим в очередь, а основной поток в этот период не будет блокироваться. - Подождите, пока все задачи синхронизации в основном потоке будут выполнены, это пройдет
event loopВозьмите его с начала в очереди и выполните его в стеке выполненияevent loopникогда не ломайся. - Весь процесс выше
Event Loop(механизм цикла событий).
(2) Микрозадачи и макрозадачи
- При каждом выполнении синхронной задачи стека выполнения выполненная асинхронная задача будет выниматься из очереди задач, но очередь разбивается на микрозадачи
microtaskи макро задачиtasksочередь - Подождите, пока не будут выполнены все микрозадачи.
microtaskвсе выполнены, обратите внимание, чтовсе, он начнет с задачи макросаtasksПолучить события из очереди. - Подождите, пока одно из событий в очереди не будет извлечено и помещено в стек выполнения, чтобы завершить выполнение, даже если цикл завершится.
- Позже
event loopПродолжит цикл, снова пойдет на микро заданиеmicrotaskВыполнить все задачи, прежде чем начинать с задач макросаtasksВозьмите один из очереди и повторите цикл.
4. Почему nextTick должен максимально отдавать приоритет микрозадачам?
простая памятьeventLoop, микрозадачи и макрозадачи, мы должны сделать еще один вывод.
Мы обнаружили, что операция рендеринга также выполняется после выполнения микрозадачи! ! ! (Конечно, не каждый раз, но по крайней мере порядок, в котором мы можем быть уверены).
- в раунде
event loopИзмените одно и то же несколько раз вdom, будет нарисован только последний. - Обновление рендера (
Update the rendering)Будет вevent loopсерединаtasksа такжеmicrotasksПосле завершения, но не каждый раундevent loopобновит рендеринг, в зависимости от того, был ли он измененdomи считает ли браузер необходимым немедленно представить новое состояние пользователю в этот момент. Если время одного кадра (время не точно, потому что количество кадров в секунду в браузере всегда колеблется, 16,7 мс - это просто неточная оценка) изменилось во многих местахdom, браузер может накапливать изменения и рисовать только один раз, что разумно. - Если вы хотите, чтобы каждый раунд
event loopизменения представлены в режиме реального времени и могут быть использованыrequestAnimationFrame.
Вот подкидываю вывод, причины и теоретические познания можно посмотреть в этой статьеИзучите время асинхронного рендеринга javaScript и обновления браузера из спецификации цикла событий., этот великий бог написал очень хорошо.
Я не знаю, догадались ли вы [nextTickЗачем пытаться сделать как можно большеmicrotaskприоритет? 】
Вот опять я украл картину Великого Бога,event loopГрубый цикл процесса:
Предположим, теперь, когда выполняется некая задача, мы будем пакетироватьdomЧтобы сделать асинхронные модификации, мы вставляем эту задачу вtasks, то есть с помощью задачи макроса для достижения.
Очевидно, что в этом случае, еслиtaskВ очереди много очередей, и многократно встречающиеся очереди микрозадач выполняются одновременно. Это, скорее всего, вызовет несколько рендерингов браузера, но все равно не выполнит наши реальные изменения.domЗадача.
В этом случае не только задержится обновление представления, но и возникнут проблемы с производительностью. Это также может вызвать некоторые странные проблемы с представлением.
Таким образом, эта задача вставляетmicrotasks:
taskЕсли в очереди есть большое количество задач, ожидающих выполнения, онаdomменяется какmicrotasksвместо задачи макроса(task) может быстрее представлять изменения пользователю.
Справочная статья
В статье есть некоторые выводы, которые напрямую ссылаются на другие статьи, мне правда лень это писать~~
- Базовое событие собеседования узла? Микрозадачи, макрозадачи? Возьми тебя в полет
- Изучите время асинхронного рендеринга javaScript и обновления браузера из спецификации цикла событий.
Нарушение удалить ^^