ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Vue中的$nextTick

2022-08-05 00:03:02  阅读:197  来源: 互联网

标签:nextTick el Vue 函数 DOM callbacks Promise


  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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有