nextTick
注:本系列文章是对Vue源码学习的一个总结笔记,如有侵犯您的隐私请联系我修改或者删除。 参考
书籍:vue权威指南
本文笔记整理以及原理参考博客:Vue $nextTick 原理、vue nextTick深入理解-vue性能优化、DOM更新时机、事件循环机制、Vue.nextTick 的原理和用途。
nextTick
的由来:
vue
实现响应式并不是数据发生变化之后DOM
立即变化,而是等同一事件循环中所有的数据变化完成后,再统一更新。这也是Vue的一个重要的概念:异步更新队列(JS运行机制 、 事件循环)。nextTick
的使用场景:
在数据获取后,需要对新视图进行下一步操作或者其他操作的时候,发现获取不到DOM
元素。
源码解析
-
定义变量
const callbacks = [];//缓存数组函数 let pending = false;//是否在执行 let timerFunc;//保存要执行的函数
-
创建
$nextTick
内实际调用的函数function flushCallbacks () { pending = false//是否在执行-->否 const copies = callbacks.slice(0)//复制函数的数组副本 callbacks.length = 0//函数数组清空 for (let i = 0; i < copies.length; i++) { copies[i]() }//依次执行函数 }
-
Vue
会根据当前浏览器环境是否支持使用原生的Promise.then
和MutationObserver
(实质是一个可以监听DOM变化的接口),如果都不支持,就会采用setTimeout
代替,目的是 延迟函数到 DOM 更新后再使用。promise.then的延时调用
if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) if (isIOS) setTimeout(noop) } isUsingMicroTask = true }
在浏览器支
Promise
的情况下,Promise.then方法可以将函数延迟到当前函数调用栈最末端,也就是最后调用该函数。从而做到延迟。MutationObserver的延时调用
else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true }
MutationObserver
功能是监听dom节点的变动,在所有dom变动完成后,执行回调函数。
具体有一下几点变动的监听:
childList:子元素的变动
attributes:属性的变动
characterData:节点内容或节点文本的变动
subtree:所有下属节点(包括子节点和子节点的子节点)的变动
这样我们就可以使用MutationObserver
监听dom节点变化,在dom节点变化完成后再执行函数。setTimeOut的延时调用
else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { timerFunc = () => { setTimeout(flushCallbacks, 0) } }
setTimeout(func,0)会将func函数延迟到下一次回调函数的开始,也就是当前函数执行完毕后再执行该函数。
-
闭包函数
//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; }) } }