vue的nextTick原理
定义:根据官方文档的解释,它可以在DOM更新完毕之后执行一个回调// 修改数据vm.msg = 'Hello'// DOM 还没有更新Vue.nextTick(function () {// DOM 更新了})使用场景vue中异步加载数据,数据更新 => dom更新后才能进行 scroll-better 的初始化操作。在Vue生命周期的cre...
定义:
根据官方文档的解释,它可以在DOM更新完毕之后执行一个回调
// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})
使用场景
vue中异步加载数据,数据更新 => dom更新后才能进行 scroll-better 的初始化操作。
在Vue生命周期的created()
钩子函数进行的DOM操作一定要放在Vue.nextTick()
的回调函数中
介绍
nextTick是全局vue的一个函数,在vue系统中,用于处理dom更新的操作。vue里面有一个watcher,用于观察数据的变化,然后更新dom,vue里面并不是每次数据改变都会触发更新dom,而是将这些操作都缓存在一个队列,在一个事件循环结束之后,刷新队列,统一执行dom更新操作。
在vue生命周期的created()钩子函数进行的DOM操作要放在Vue.nextTick()的回调函数中,因为created()钩子函数执行的时候DOM并未进行任何渲染,而此时进行DOM操作是徒劳的,所以此处一定要将DOM操作的JS代码放进Vue.nextTick()的回调函数中。
而与之对应的mounted钩子函数,该钩子函数执行时所有的DOM挂载和渲染都已完成,此时该钩子函数进行任何DOM操作都不会有个问题。
什么时候dom更新完成?
原理:利用异步队列
在每个 macro-task 运行完以后,UI 都会重渲染,那么在 miscro-task (异步事件回调) 中就完成数据更新,当前 次事件循环 结束就可以得到最新的 UI 了。反之如果新建一个 macro-task 来做数据更新,那么渲染就会进行两次。
vue的降级策略(兼容)
(micro-task)promise -> MutationObserver ->(macro-task) setTimeout
想要要创建一个新的 job,优先使用 Promise,如果浏览器不支持,再尝试 MutationObserver。实在不行,只能用 setTimeout 创建 task 了。
MutationObserver原理?
MutationObserver 是 h5 新加的一个功能,其功能是监听dom节点的变动,在所有 dom 变动完成后,执行回调函数。
具体有一下几点变动的监听:
childList:子元素的变动
attributes:属性的变动
characterData:节点内容或节点文本的变动
subtree:所有下属节点(包括子节点和子节点的子节点)的变动
//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)
}
}
如果检测到浏览器支持MutationObserver,则创建一个文本节点,监听这个文本节点的改动事件,把回调放进micro-task 中, 等DOM更新完毕后,执行此回调(nextTickHandler)。
问:为什么自己创建的文本节点更新完毕,就能代表其他DOM节点更新完毕呢?
答:js事件循环机制
vue2.5的降级策略
上面我们讲到了,队列控制的最佳选择是microtask,而microtask的最佳选择是Promise.但microtask: Promise , MutatioObserver
如果当前环境不支持Promise,MutatioObserver,vue就不得不降级为macrotask来做队列控制了。
macrotask:setImmediate、MessageChannel、setTimeout.
setImmediate是最理想的方案了,可惜的是只有IE和nodejs支持。
MessageChannel的onmessage回调也是microtask,但也是个新API,面临兼容性的尴尬...
所以最后的兜底方案就是setTimeout了,尽管它有执行延迟,可能造成多次渲染,算是没有办法的办法了。
vue.nextTcik源码分析
var nextTick=(function () {
//存储需要触发的回调函数
var callbacks=[];
//是否正在等待的标志(false:允许触发在下次事件循环触发callbacks中的回调,
// true: 已经触发过,需要等到下次事件循环)
var pending=false;
//设置在下次事件循环触发callbacks的触发函数
var timerFunc;
//处理callbacks的函数
function nextTickHandler() {
// 可以触发timeFunc
pending=false;
//复制callback
var copies=callbacks.slice(0);
//清除callback
callbacks.length=0;
for(var i=0;i<copies.length;i++){
//触发callback的回调函数
copies[i]();
}
}
//如果支持promise,使用promise实现
if(typeof Promise !=='undefined' && isNative(promise)){
var p=Promise.resolve();
var logError=function (err) {
console.error(err);
};
timerFunc=function () {
p.then(nextTickHandler).catch(logError);
//iOS的webview下,需要强制刷新队列,执行上面的回调函数
if(isIOS) {setTimeout(noop);}
};
// 如果Promise不支持,但支持MutationObserver
// H5新特性,异步,当dom变动是触发,注意是所有的dom都改变结束后触发
} else 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=function () {
counter=(counter+1)%2;
textNode.data=String(counter);
};
} else {
//上面两种都不支持,用setTimeout
timerFunc=function () {
setTimeout(nextTickHandler,0);
};
}
//nextTick接收的函数,参数1:回调函数 参数2:回调函数的执行上下文
return function queueNextTick(cb,ctx) {
//用于接收触发Promise.then中回调的函数
//向回调函数中pushcallback
var _resolve;
callbacks.push(function () {
//如果有回调函数,执行回调函数
if(cb) {cb.call(ctx);}
//触发Promise的then回调
if(_resolve) {_resolve(ctx);}
});
//是否执行刷新callback队列
if(!pending){
pending=true;
timerFunc();
}
//如果没有传递回调函数,并且当前浏览器支持promise,使用promise实现
if(!cb && typeof Promise !=='undefined'){
return new Promise(function (resolve) {
_resolve=resolve;
})
}
}
})
原理总结
以上就是vue的nextTick方法的实现原理了,总结一下就是:
- vue用异步队列的方式来控制DOM更新和nextTick回调先后执行
- micro-task因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
- 因为兼容性问题,vue不得不做了microtask向macrotask的降级方案
更多推荐
所有评论(0)