Vue中的$nextTick
作者:互联网
-
nextTick
官方定义:在下次
DOM
更新循环结束之后执行回调,在修改数据之后立即使用这个方法,获取更新后的DOM
。Vue
在更新DOM
时是异步执行的。当数据发生变化。Vue
将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,在统一进行更新。举个栗子:
HTML
结构<div id="app"> {{message}} </div>
构建一个
vue
实例const vm = new Vue({ el:'#app', data:{ message:'旧值' } })
修改
message
this.message = '新值'
这时想获取页面的
DOM
节点,发现获取的是旧值console.log(vm.$el.textContent) //旧值
这是因为
message
数据在发生变化的时候,vue
并不会立刻去更新DOM
,而是将修改的操作放在一个异步操作队列中。如果一直修改数据,异步操作队列还会进行去重。等到同一事件循环中的所有数据变化完成之后,会将队列中的事件进行处理,进行
DOM
的更新为什么要有nextTick
举个栗子
{{num}} for(let i = 0;i < 100000;i++){ num = i }
如果没有
nextTick
更新机制,那么num
每次更新值都会触发视图更新,有了nextTick
机制,只需要更新一次,所以nextTick
本质是一种优化策略。 -
使用场景
如果想要在修改数据后立即得到更新后的
DOM
结构,可以使用Vue.nextTick()
第一个参数为:回调函数(可以获取最近的
DOM
结构)第二个参数为:执行函数上下文
//修改数据 vm.message = '修改后的值' //DOM还没有更新 console.log(vm.$el.textContent) //原始的值 Vue.nextTick(function(){ //DOM更新了 console.log(vm.$el.textContent) //修改后的值 })
组件内使用
vm.$nextTick()
实例方法只需要通过this.$nextTick()
,并且回调函数中的this
将自动绑定到当前的Vue
实例上this.message = '修改后的值' console.log(this.$el.textContent) // => '原始的值' this.$nextTick(function(){ console.log(this.$el.textContent) // => '修改后的值' })
$nextTick()
会返回一个Promise
对象,可以使用async/await
完成相同作用的事情this.message = '修改后的值' console.log(this.$el.textContent) // => '原始的值' await this.$nextTick() console.log(this.$el.textContent) // => '修改后的值'
-
实现原理
源码位置:
/src/core/util/next-tick.js
callbacks
也就是异步操作队列callbacks
新增回调函数后又执行了timeFunc
函数,pending
是用来标志同一个时间只能执行一次export function nextTick(cb ?: Function,ctx ?: Object){ let _resolve //cb回调函数会经统一处理压入 callbacks 数组 callbacks.push(() => { if(cb){ //给 cb 回调函数执行加上了 try-catch 错误处理 try{ cb.call(ctx) }catch(e){ handleError(e,ctx,'nextTick') } }else if(_resolve){ _resolve(ctx) } }) //执行异步延迟函数 timerFunc if(!pending){ pending = true timerFunc() } //当 nextTick 没有传入函数参数的时候,返回一个 Promise 化的调用 if(!cb && typeof Promise !== 'undefined'){ return new Promise(resolve => { _resolve = resolve }) } }
timerFunc
函数定义,这是根据当前环境确定的,分别有:Promise.then
、MutationObserver
、setImmediate
、setTimeout
通过上面任意一种方法进行降级操作
export let isUsingMicroTask = false if(typeof Promise !== 'undefined' && isNative(Promise)){ //判断:是否原生支持Promise const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) if(isIOS) setTimeout(noop) } isUsingMicroTask = true }else if(!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString() === ['object MutationObserverConstructor'])){ //判断:是否原生支持MutationObserver 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 }else if(typeof setImmediate !== 'undefined' && isNative(setImmediate)){ //判断:是否原生支持setImmediate timerFunc = () => { setImmediate(flushCallbacks) } }else{ //判断:若都不行,直接使用setTimeout timerFunc = () => { setTimeout(flushCallbacks,0) } }
无论是微任务还是宏任务,都会放到
flushCallbacks
使用这里将
callbacks
里面的函数复制一份,同时callbacks
置空依次执行
callbacks
里面的函数function flushCallbasks(){ pending = false const copies = callbacks.slice(0) callbacks.length = 0 for(let i = 0;i < copies.length;i++){ copies[i]() } }
-
小结
- 把回调函数放入callbacks等待执行
- 将执行函数放到微任务或者宏任务中
- 事件循环到了微任务或者宏任务,执行函数依次callbacks中的回调
参考函数:
https://vue3js.cn/interview/vue/nexttick.html
标签:nextTick,el,Vue,函数,DOM,callbacks,Promise 来源: https://www.cnblogs.com/shallow-dreamer/p/16552704.html