Определение следующего тика
Vue.nextTick([callback, context])
Есть два варианта действий после обновления DOM:
- Через объект MO отслеживайте изменения DOM и выполняйте их в своем cb.
- Выполнить, контролируя очередь задач
Для повышения производительности VUE не обновляет DOM при каждом изменении. Вместо этого обновляйте DOM асинхронно.
Таким образом, время рендеринга VUE находится между завершением выполнения каждой очереди задач. Это макрозадача или микрозадача, которая изменяет данные или изменяет операцию DOM в каждой очереди задач.
Очередь задач 1 завершена -> обновить DOM -> очередь задач 2 завершена -> обновить DOM....
достичь ключевых точек
- Чтобы получить DOM после обновления, вы должны поставить cb в nextTick в следующую очередь задач (макро-задачи и микро-задачи в порядке)
- Микрозадачи всегда выполняются до выполнения макрозадач. Поэтому микрозадачи больше подходят для сценариев nextTick. Поместите скрипт обратного вызова nextTick в promise.then(), чтобы гарантировать его выполнение после обновления DOM.
- Политика понижения версии VUE (из-за проблем с совместимостью API), (promise -> setTimeout)
Зачем использовать Vue.nextTick()
Во-первых, давайте разберемся, как работает JS.
- Рабочий механизм JS (Event Loop)
- Выполнение JS является однопоточным и основано на цикле событий.
цикл событий
- Все задачи синхронизации выполняются в основном потоке, образуя стек выполнения.
- Вне основного потока будет очередь задач, и пока у асинхронной задачи есть результат, событие будет помещено в очередь задач.
- Основной поток продолжает повторять третий шаг.
Процесс выполнения основного потока здесь — тик, а все асинхронные результаты планируются через очередь задач. Цикл событий делится на макро-задачи и микро-задачи. Независимо от того, выполняются ли макро-задачи или микро-задачи, он вводит тик после завершения и выполняет рендеринг пользовательского интерфейса между двумя тиками.
Поскольку обновления Vue DOM выполняются асинхронно, то есть при изменении данных, представление не будет обновляться сразу, а будет отслеживать изменения данных и кэшировать их в том же цикле событий. будут унифицированы. Посмотреть обновление. Чтобы убедиться, что вы получаете обновленный DOM, установлен метод Vue.nextTick().
Что оно делает:
Vue.nextTick() используется для получения обновленного DOM. Время срабатывания: после изменения данных в том же цикле событий DOM обновляется и немедленно выполняется обратный вызов Vue.nextTick().
同一事件循环中的代码执行完毕 -> DOM 更新 -> nextTick callback触发
nextОтметьте использование
- Передавая обратный вызов
- через .then()
//改变数据
vm.message = 'changed'
//想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新
console.log(vm.$el.textContent) // 并不会得到'changed'
//这样可以,nextTick里面的代码会在DOM更新后执行
Vue.nextTick(function(){
// DOM 更新了
//可以得到'changed'
console.log(vm.$el.textContent)
})
// 作为一个 Promise 使用 即不传回调
Vue.nextTick()
.then(function () {
// DOM 更新了
})
Сценарии применения:
Вам нужно работать на основе нового представления после обновления представления.
-
Операции DOM, выполняемые в функции-ловушке created() жизненного цикла Vue, должны быть помещены в функцию обратного вызова Vue.nextTick(). Причина: DOM на самом деле не визуализируется при выполнении функции-ловушки created().
-
Операция, которая должна выполняться после изменения данных, и эта операция должна использовать структуру DOM, которая изменяется при изменении данных, эта операция должна быть помещена в функцию обратного вызова Vue.nextTick().
Причина: Vue выполняет обновления DOM асинхронно. Пока наблюдаются изменения данных, Vue открывает очередь и буферизует все изменения данных, происходящие в одном и том же цикле событий. Если один и тот же наблюдатель запускается несколько раз, он будет помещен только в очередь один раз.
Анализ версий
- Версия 2.6 предпочитает использовать MicroTask в качестве асинхронной обертки задержки, а метод записи относительно прост.
- 2.5 В версии 2.5 Реализация NextTick реализуется MicroTimerfunc, Macrotimerfunc Комбинация, приоритет вызова задержки: обещание> Setimmediate> MessageChannel> Settimeout, исходный код.
Другие сценарии применения
- Нажатие на кнопку показывает поле ввода, которое было скрыто с помощью v-show = false, и получает фокус.
showsou(){
this.showit = true //修改 v-show
document.getElementById("keywords").focus() //在第一个 tick 里,获取不到输入框,自然也获取不到焦点
}
изменить на:
showsou(){
this.showit = true
this.$nextTick(function () {
// DOM 更新了
document.getElementById("keywords").focus()
})
}
- Нажмите, чтобы получить ширину элемента.
<div id="app">
<p ref="myWidth" v-if="showMe">{{ message }}</p>
<button @click="getMyWidth">获取p元素宽度</button>
</div>
getMyWidth() {
this.showMe = true;
//this.message = this.$refs.myWidth.offsetWidth;
//报错 TypeError: this.$refs.myWidth is undefined
this.$nextTick(()=>{
//dom元素更新后执行,此时能拿到p元素的属性
this.message = this.$refs.myWidth.offsetWidth;
})
}
showsou(){
this.showit = true
this.$nextTick(function () {
// DOM 更新了
document.getElementById("keywords").focus()
})
}
Анализ исходного кода
Реализация nextTick имеет отдельный JS-файл для его обслуживания в src/core/util/next-tick.js. Исходный код nextTick в основном разделен на две части: определение возможностей и выполнение очереди обратного вызова разными способами в зависимости от обнаружения возможностей.
Тестирование способностей
Поскольку макрозадачи занимают больше времени, чем микрозадачи, микрозадачи предпочтительнее, если они поддерживаются браузером. Если браузер не поддерживает микрозадачи, используйте макрозадачи.
// 空函数,可用作函数占位符
import { noop } from 'shared/util'
// 错误处理函数
import { handleError } from './error'
// 是否是IE、IOS、内置函数
import { isIE, isIOS, isNative } from './env'
// 使用 MicroTask 的标识符,这里是因为火狐在<=53时 无法触发微任务,在modules/events.js文件中引用进行安全排除
export let isUsingMicroTask = false
// 用来存储所有需要执行的回调函数
const callbacks = []
// 用来标志是否正在执行回调函数
let pending = false
// 对callbacks进行遍历,然后执行相应的回调函数
function flushCallbacks () {
pending = false
// 这里拷贝的原因是:
// 有的cb 执行过程中又会往callbacks中加入内容
// 比如 $nextTick的回调函数里还有$nextTick
// 后者的应该放到下一轮的nextTick 中执行
// 所以拷贝一份当前的,遍历执行完当前的即可,避免无休止的执行下去
const copies = callbcks.slice(0)
callbacks.length = 0
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc // 异步执行函数 用于异步延迟调用 flushCallbacks 函数
// 在2.5中,我们使用(宏)任务(与微任务结合使用)。
// 然而,当状态在重新绘制之前发生变化时,就会出现一些微妙的问题
// (例如#6813,out-in转换)。
// 同样,在事件处理程序中使用(宏)任务会导致一些奇怪的行为
// 因此,我们现在再次在任何地方使用微任务。
// 优先使用 Promise
if(typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// IOS 的UIWebView, Promise.then 回调被推入 microTask 队列,但是队列可能不会如期执行
// 因此,添加一个空计时器强制执行 microTask
if(isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if(!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString === '[object MutationObserverConstructor]')) {
// 当 原生Promise 不可用时,使用 原生MutationObserver
// e.g. PhantomJS, iOS7, Android 4.4
let counter = 1
// 创建MO实例,监听到DOM变动后会执行回调flushCallbacks
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true // 设置true 表示观察目标的改变
})
// 每次执行timerFunc 都会让文本节点的内容在 0/1之间切换
// 切换之后将新值复制到 MO 观测的文本节点上
// 节点内容变化会触发回调
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter) // 触发回调
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
Приоритеты отложенных вызовов следующие:
Promise > MutationObserver > setImmediate > setTimeout
Ссылаться на
Анализ кода модификации данных Vue:
- Непосредственно изменяйте данные в Vue, данные, чтобы обновлять то, что происходит в DOM.
- Что делает nextTick?
Предположим, текущий код шаблона
<div id="a">{{a}}</div>
В это время мы пишем следующий код в смонтированный хук
this.a = 'a';
this.$nextTick(function(){
console.log($('#a')[0].textContent);
})
В коде задач данные могут быть изменены несколько раз. И мы не можем сразу уведомить наблюдателя, чтобы обновить DOM каждый раз, когда он модифицирован. Альтернатива - добавить наблюдателя на массив обновления. После выполнения кода задача выполнена (то есть все коды синхронизации выполнены), это означает, что этот раунд модификации данных закончился. В это время мы можем вызвать операцию обновления наблюдателя, поэтому независимо от того, сколько раз требуется код задач ранее, мы обновим только DOM один раз в конце.
NextTick фокусируется на том, чтобы поставить FlushbatcherQueue для перевода операции Watcher в Microtask, как для использования в Mo или Promise. Тогда это не имеет значения. Оба нельзя заставить использовать SetTimeout в совместимой с ним среде. Но settimeout помещает функцию обратного вызова в очередь макрозадач, а браузер запускает рендеринг пользовательского интерфейса при очистке очереди микрозадач, поэтому settingsTimeout тратит впустую свою предыдущую возможность рендеринга пользовательского интерфейса браузера. (То есть как минимум два рендеринга пользовательского интерфейса могут отображать только обновленный DOM)
Настройка времени обновления Vue DOM
В конце каждого цикла событий будет этап рендеринга пользовательского интерфейса, который заключается в обновлении 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 хорошо продумано (и претерпело множество изменений). , макрозадача может быть выполнена только после выполнения всех микрозадач, а микрозадача имеет более высокий приоритет.
Эта функция MicroTask - это просто лучший выбор для контроля очереди! Vue также называется рядом с асинхронным управлением очередей внутри обновления DOM. И когда мы вызываем NextTick, он добавляет наш собственный обратный вызовов после обновления микрозазки DOM, обеспечение того, чтобы наш код выполнен после обновления DOM, и он также может избежать нескольких проблем выполнения, которые могут существовать.
Общие микрозадачи: 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.
MessageChannel Of OnMessage Callback - MicroTask, но и новый API, столкнувшись с смущением совместимости ...
краткое содержание
Выше приведен принцип реализации метода nextTick vue.
- Vue использует асинхронную очередь для управления обновлением DOM и обратным вызовом nextTick для последовательного выполнения.
- Микрозадача может гарантировать, что микропереход в очереди будет выполнен до завершения цикла события.
- Из-за совместимости VUE должен выполнить понижение версии Microtask для Macrotask.
Суммировать:
- nextTick, добавить микрозадачу или макрозадачу в очередь задач. (приоритет микрозадач обещаний)
- nextTick, vue — такая идея, он не использует MO для мониторинга изменений DOM, а использует управление очередью для достижения цели. Так как же Vue обеспечивает управление очередью? Естественно, мы можем думать о setTimeout и помещать код, который будет выполняться nextTick, в качестве следующей задачи и помещать его в конец очереди. это еще не все
- Лучший выбор для управления очередью — микрозадача, а лучший выбор для микрозадачи — Promise.
- Vue выполняет обновления DOM асинхронно
- Пока Vue наблюдает за изменениями (захваченными данными), Vue открывает очередь для хранения изменений данных, происходящих в том же цикле событий.
- nextTick фактически использует механизм очереди задач для обновления DOM.
- В IOS обратный вызов promise.then не может быть выполнен должным образом, добавьте пустой таймер, чтобы принудительно выполнить микрозадачу
- Рендеринг Vue — это рендеринг пользовательского интерфейса между двумя тиками.
- Изменить данные -> открыть очередь (сохранить все изменения данных) -> уведомить наблюдателя -> обновить DOM между двумя тиками в соответствии с механизмом цикла событий
- Цикл событий (основной поток продолжает повторять третий шаг)
- Задачи синхронизации выполняются в основном потоке (формируя стек выполнения) ->
- После завершения выполнения задачи синхронизации выполнение (очередь заданий) асинхронной задачи ->
- Выполнять микрозадачи перед макрозадачами
В Vue this.a="a" данные изменены, процесс упрощенной версии:
- Через набор ловушек defineproperty уведомляются все наблюдатели, подписавшиеся на данные.Вочер получает уведомление и помещает изменение данных в массив обновлений (очередь задач).После завершения выполнения задачи синхронизации основного потока асинхронный задача выполняется для обновления DOM. (Обновление dom в vue происходит асинхронно).
Это заставит MO добавить свою функцию обратного вызова в очередь микрозадач.
- Когда объект MO обнаруживает обновление DOM, он не вызывает обратный вызов напрямую, а помещает обратный вызов в микрозадачу, и после завершения выполнения микрозадачи он выполняет действия после обновления DOM (скрипт в nextTick начинает выполняться).
Уточните процесс (VUE не использует объекты MO, поэтому этот процесс необходимо настроить на 6, 7, 8, 9, 10 шагах)
- а изменен
- Данные отслеживают, что изменяется
- Уведомляет наблюдателей, подписавшихся на данные.
- наблюдатель уведомил наблюдателя в массив обновлений. жду обновления
- Объект MO обнаруживает, что DOM был изменен
- Поместите его обратный вызов в очередь микрозадачи и дождитесь выполнения.
- Вы использовали NextTick?
- Если это так: nextTick преобразует микрозадачу в синхронную задачу для выполнения.
- Если нет: следуйте обычному циклу событий, чтобы обновить DOM.
6,7,8,9,10 вышеуказанные процессы необходимо изменить следующим образом:
- После завершения рендеринга DOM начнется следующая очередь задач.
- Настройте очередь задач и поместите cb в nextTick в очередь задач для выполнения.
- Стратегия деградации очереди задач (сначала микрозадачи, потом макрозадачи)