используемые сцены:
Когда мы разрабатываем проект, мы всегда сталкиваемся с некоторыми сценариями: когда мы используем операцию vue для обновления DOM, нам нужно выполнить некоторые операции с новым DOM, но в это время мы часто не можем получить новый DOM, потому что в это время , dom не подвергался повторному рендерингу, поэтому воспользуемся методом vm.$nextTick.
Применение:
nextTick принимает функцию обратного вызова в качестве параметра, и его роль заключается в задержке выполнения обратного вызова после следующего цикла обновления DOM.
methods:{
example:function(){
//修改数据
this.message='changed'
//此时dom还没有跟新,不能获取新的数据
this.$nextTick(function(){
//dom现在跟新了
//可以获取新的dom数据,执行操作
this.doSomeThing()
})
}
}
Маленькие мысли:
При использовании мы обнаруживаем, что выполняется после следующего цикла обновления DOM и когда именно, поэтому нам нужно понять, что такое цикл обновления DOM. В Vue при изменении состояния просмотра наблюдатель будет уведомлен, а затем инициирует процесс рендеринга виртуального DOM.Операция рендеринга не синхронная, а асинхронная. В Vue есть очередь, и каждый раз при рендеринге наблюдатель будет помещаться в эту очередь, а в следующем цикле событий наблюдатель инициирует процесс рендеринга.
Почему Vue использует асинхронную очередь обновлений?
Проще говоря, это повышение производительности и повышение эффективности. Мы знаем, что Vue2.0 использует виртуальный DOM для рендеринга, уведомление об обнаружении изменений отправляется только компоненту, любое изменение в компоненте будет уведомлено наблюдателю, а затем виртуальный DOM будет сравнивать весь компонент (алгоритм сравнения, Я подробно изучу его, когда у меня будет время в будущем), а затем обновлю DOM.Если два данных изменяются в одном и том же цикле событий, наблюдатель компонента получит два уведомления, в результате чего будет два рендеринга (синхронизированный и два рендеринга), на самом деле нам не нужно рендерить так много раз, нам просто нужно рендерить DOM всего компонента до последнего за один раз после того, как все состояния изменены.
Как решить проблему, когда для нескольких изменений состояния компонента цикла событий требуется только одно обновление рендеринга?
На самом деле это очень просто, то есть добавить полученный экземпляр наблюдателя в очередь и закэшировать его, а перед добавлением очереди проверить, существует ли уже такой же наблюдатель в очереди. Если он не существует, экземпляр наблюдателя добавляется в очередь. Затем в следующем цикле событий Vue позволит наблюдателям в этой очереди запустить рендеринг и очистить очередь. Это гарантирует, что множественные изменения состояния компонента цикла событий требуют только одного обновления рендеринга.
Что такое цикл событий?
Мы знаем, что js — это однопоточный неблокирующий язык сценариев, а это значит, что при выполнении кода js есть только один основной поток для обработки всех задач. Неблокирующий означает, что когда коду нужно обработать асинхронную задачу, основной поток будет приостановлен (в ожидании), а когда асинхронная задача будет обработана, основной поток выполнит обратный вызов по определенным правилам. Фактически, когда задача будет выполнена, js добавит это событие в очередь (event queue). Событие, помещенное в очередь, не выполнит свой обратный вызов немедленно, но после выполнения всех задач в текущем стеке выполнения основной поток проверит, есть ли задача в очереди событий.
Существует два типа асинхронных задач: микрозадачи и макрозадачи. Различные типы задач назначаются разным очередям задач.
После выполнения всех задач в стеке выполнения основной поток проверяет, есть ли задача в очереди событий, и если есть, то выполняет по очереди все коллбэки в очереди, пока она не опустеет. Затем перейдите в очередь задач макросов, чтобы удалить событие, добавьте соответствующий обратный вызов в текущий стек выполнения, все задачи в текущем стеке выполнения выполнены и проверьте, есть ли событие в очереди микрозадач. Беспроводное зацикливание этого процесса называется циклом событий.
общие микрозадачи
- Promise.then
- Object.observe
- MutationObserver
Общие задачи макроса
- setTimeout
- setInterval
- setImmediate
- События взаимодействия с пользовательским интерфейсом
Когда мы используем vm.$nextTick для получения обновленного DOM, мы должны использовать nextTick для регистрации обратного вызова после изменения данных.
methods:{
example:function(){
//修改数据
this.message='changed'
//此时dom还没有跟新,不能获取新的数据
this.$nextTick(function(){
//dom现在跟新了
//可以获取新的dom数据,执行操作
this.doSomeThing()
})
}
}
Если обратный вызов сначала зарегистрирован с помощью nextTick, а затем данные изменены, то обратный вызов, зарегистрированный с помощью nextTick, сначала выполняется в очереди микрозадач, а затем выполняется обратный вызов с новым DOM, поэтому новый DOM не может быть получен в обратном вызове. потому что не обновлялся.
methods:{
example:function(){
//此时dom还没有跟新,不能获取新的数据
this.$nextTick(function(){
//dom没有跟新,不能获取新的dom
this.doSomeThing()
})
//修改数据
this.message='changed'
}
}
Мы знаем, что добавленный в очередь микрозадач механизм выполнения задач выше, чем механизм выполнения макрозадач (следующий код надо понимать)
methods:{
example:function(){
//先试用setTimeout向宏任务中注册回调
setTimeout(()=>{
//现在DOM已经跟新了,可以获取最新DOM
})
//然后修改数据
this.message='changed'
}
}
setTimeout — это задача макроса. Использование его для регистрации обратных вызовов будет добавлено в задачу макроса. Задача макроса выполняется позже, чем микрозадача, поэтому, даже если она зарегистрирована первой, обратный вызов устанавливается в setTineout после выполнения нового DOM .
Поняв роль nextTick, давайте представим принцип реализации
Анализ принципа реализации:
Поскольку nextTick добавит обратный вызов в очередь задач для отложенного выполнения, до того, как обратный вызов будет выполнен, если вы используете nextTick повторно, Vue не добавит обратный вызов в очередь задач, а только добавит одну задачу. Внутри Vue есть список для хранения обратных вызовов, указанных в параметре nextTick. Когда задача запускается, все обратные вызовы в списке выполняются, и список очищается. Код выглядит следующим образом (упрощенная версия):
const callbacks=[]
let pending=false
function flushCallBacks(){
pending=false
const copies=callbacks.slice(0)
callbacks.length=0
for(let i=0;i<copies.length;i++){
copies[i]()
}
}
let microTimeFun
const p=Promise.resolve()
microTimeFun=()=>{
p.then(flushCallBacks)
}
export function nextTick(cb,ctx){
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}
})
if(!pending){
pending=true
microTimeFun()
}
}
Понимание связанных переменных:
- обратные вызовы: используется для хранения функции обратного вызова, зарегистрированной пользователем (получает операцию, выполняемую DOM после обновления)
- pending: используется, чтобы указать, добавлять ли задачу в очередь задач. Если pending имеет значение false, это означает, что в очереди задач нет задачи nextTick, и нужно добавить задачу nextTick. true.Если есть nextTick перед выполнением обратного вызова, это не Задачи будут повторно добавляться в очередь задач.Когда функция обратного вызова начинает выполняться, ожидание является флэйсом, и выполняется новый раунд цикла событий.
- flushCallbacks: это то, что мы называем задачами, зарегистрированными в очереди задач.При выполнении этой функции все функции в обратных вызовах выполняются по очереди, затем обратные вызовы очищаются, а ожидание сбрасывается на false.Поэтому в событии loop, flushCallbacks будет выполняться только один раз.
- microTimerFunc: он использует Promise.then для добавления flushCallbacks в очередь микрозадач.
На следующем рисунке показан внутренний процесс регистрации и процесс выполнения nextTick.
this.$nextTick().then(function(){
//dom跟新了
})
Чтобы выполнить эту функцию, вам нужно только судить в nextTIck. Если обратный вызов не предоставляется и в настоящее время поддерживается Promise, верните Promise и добавьте функцию в обратные вызовы. Когда эта функция выполняется, выполните разрешение Promise. Код следующим образом
function nextTick (cb, ctx) {
var _resolve;
callbacks.push(function () {
if (cb) {
cb.call(ctx);
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc();
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;
})
}
}
просмотр исходного кода nextTick
На этом принцип nextTick в основном закончен. Теперь мы можем посмотреть на исходный код nextTick в реальном vue, вероятно, мы все это понимаем, исходный код выглядит следующим образом.
var timerFunc;
// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
timerFunc = function () {
p.then(flushCallbacks);
// In problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) { setTimeout(noop); }
};
isUsingMicroTask = true;
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
var counter = 1;
var observer = new MutationObserver(flushCallbacks);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
isUsingMicroTask = true;
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Technically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = function () {
setImmediate(flushCallbacks);
};
} else {
// Fallback to setTimeout.
timerFunc = function () {
setTimeout(flushCallbacks, 0);
};
}
function nextTick (cb, ctx) {
var _resolve;
callbacks.push(function () {
if (cb) {
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc();
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;
})
}
}
Суммировать
На написание этой статьи ушло около двух дней.Я полностью ссылаюсь на книгу