网站首页 > 技术文章 正文
$nextTick:根据官方文档的解释,它可以在 DOM 更新完毕之后执行一个回调函数,并返回一个 Promise(如果支持的话)
// 修改数据
vm.msg = "Hello";
// DOM 还没有更新
Vue.nextTick(function() {
// DOM 更新了
});
这块理解 EventLoop 的应该一看就懂,其实就是在下一次事件循环开始时开始更新 DOM,避免中间频繁的操作引起页面的重绘和回流。
这块引用官方文档:
可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
例如当设置vm.text = 'new value'时,该组件不会立即重新渲染,当刷新队列时,组件会在下一个事件循环‘tick’中更新,
<div id="example">{{message}}</div>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
一般在设置了this.xx='xx'数据后,立即得到最新的 DOM 数据时,才会用到$nextTick,因为 DOM 的更新是异步进行的,所以获取需要用到这个方法。
- Vue 异步更新策略
更新流程(源码解析)
- 当数据被修改时,watcher 会侦听到变化,然后会将变化进行入队:
/*
* Subscriber interface.
* Will be called when a dependency changes.
*/
Watcher.prototype.update = function update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
- 并使用 nextTick 方法添加一个 flushScheduleQueue 回调
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
function queueWatcher(watcher) {
var id = watcher.id;
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher);
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
// queue the flush
if (!waiting) {
waiting = true;
if (!config.async) {
flushSchedulerQueue();
return;
}
nextTick(flushSchedulerQueue);
}
}
}
- flushScheduleQueue 加入到 callback 数组中,并且异步执行
function nextTick(cb, ctx) {
var _resolve;
callbacks.push(function() {
if (cb) {
try {
cb.call(ctx); // !! cb 就是加入的回调
} catch (e) {
handleError(e, ctx, "nextTick");
}
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
// 异步执行 操作 见timerFunc
pending = true;
timerFunc();
}
// $flow-disable-line
if (!cb && typeof Promise !== "undefined") {
return new Promise(function(resolve) {
_resolve = resolve;
});
}
}
- timerFunc 操作就是异步执行了依次判断使用:Promise.then=>MutationObserver=>setImmediate=>setTimeout
var timerFunc;
if (typeof Promise !== "undefined" && isNative(Promise)) {
var p = Promise.resolve();
timerFunc = function() {
p.then(flushCallbacks);
// 1. Promise.then
if (isIOS) {
setTimeout(noop);
}
};
isUsingMicroTask = true;
} else if (
!isIE &&
typeof MutationObserver !== "undefined" &&
(isNative(MutationObserver) ||
MutationObserver.toString() === "[object MutationObserverConstructor]")
) {
// 2. MutationObserver
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)) {
// 3. setImmediate
timerFunc = function() {
setImmediate(flushCallbacks);
};
} else {
//4. setTimeout
timerFunc = function() {
setTimeout(flushCallbacks, 0);
};
}
- flushCallbacks 遍历所有的 callbacks 并执行
function flushCallbacks() {
pending = false;
var copies = callbacks.slice(0);
callbacks.length = 0;
for (var i = 0; i < copies.length; i++) {
copies[i]();
}
}
- 其中就有前面加入的 flushScheduleQueue,利用 queue 中的 watcher 的 run 方法,更新组件
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
watcher.run();
}
总结
以上就是 vue 的 nextTick 方法的实现原理了,总结一下就是:
- Vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行
- microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
- 因为兼容性问题,vue 不得不做了 microtask 向 macrotask 的降级方案
猜你喜欢
- 2024-10-29 Vue3 - 表单的输入与绑定(vue实现表单)
- 2024-10-29 67、Vue 中如何实现一个虚拟 DOM?说说你的思路(高薪常 问)
- 2024-10-29 Vue中配合clipboard.js实现点击按钮复制内容到剪切板
- 2024-10-29 「绍棠」 Vue面试整理 一(vue项目面试中怎样去说)
- 2024-10-29 深入浅出虚拟 DOM 和 Diff 算法,及 Vue2 与 Vue3 中的区别
- 2024-10-29 这大概是理解VUE的虚拟DOM最简单的文章了
- 2024-10-29 vue-这应该是最基础了吧(vue vh)
- 2024-10-29 深入了解Vue 3中onBeforeMount钩子和DOM元素的获取时机
- 2024-10-29 Vue.js教程(六)--Vue实例的属性和方法
- 2024-10-29 Vue中多个元素、组件的过渡及列表过渡的方法示例
- 11-26Win7\8\10下一条cmd命令可查得笔记本电脑连接过的Wifi密码
- 11-26一文搞懂MySQL行锁、表锁、间隙锁详解
- 11-26电脑的wifi密码忘记了?一招教你如何找回密码,简单明了,快收藏
- 11-26代码解决忘记密码问题 教你用CMD命令查看所有连接过的WIFI密码
- 11-26CMD命令提示符能干嘛?这些功能你都知道吗?
- 11-26性能测试之慢sql分析
- 11-26论渗透信息收集的重要性
- 11-26如何查看电脑连接过的所有WiFi密码
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)