其他分享
首页 > 其他分享> > Vue中的$nextTick

Vue中的$nextTick

作者:互联网

  1. 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本质是一种优化策略。

  2. 使用场景

    如果想要在修改数据后立即得到更新后的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)  // => '修改后的值'
    
  3. 实现原理

    源码位置:/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.thenMutationObserversetImmediatesetTimeout

    通过上面任意一种方法进行降级操作

    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]()
        }
    }
    
  4. 小结

    • 把回调函数放入callbacks等待执行
    • 将执行函数放到微任务或者宏任务中
    • 事件循环到了微任务或者宏任务,执行函数依次callbacks中的回调

参考函数:

https://vue3js.cn/interview/vue/nexttick.html

标签:nextTick,el,Vue,函数,DOM,callbacks,Promise
来源: https://www.cnblogs.com/shallow-dreamer/p/16552704.html