nextTick深入理解

定义 【nextTick,事件循环】

nextTick的由来:

由于VUE的数据驱动视图更新,是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

比如:

<template>
	<div>
        <button @click="handlerClick">点击</button>
        <input 
    		type='text' 
    		v-show='todo.isEdit' 
    		:value='todo.title'  
    		ref='inputTitle' 
		/>
    </div>
</template>

<script>
export default{
    data:function(){
        return {
            todo:{
                isEdit:false,
                title:"展示数据"
            }
        }
    },
    methods:{
        handlerClick(){
            this.todo.isEdit = true;
            this.$nextTick(()=>{
                this.$refs.inputTitle.focus()
            })
        }
    }
}
</script>

分析上面的代码:上面的isEdit代码初始值为false,当你失去焦点之后调用函数,在函数内部,先执行if判断,在if判断里面将isEdit改为true,那么,这个时候,按照我们的逻辑来看,页面会重新渲染,input也会展示到页面上。基于这样的结论,在你执行this.$refs...的时候,是不是直接可以为input框聚焦???

不是的。真实的场景是:在同一个事件中对data值进行改变时,页面并不会马上重新渲染页面,而是等所有的修改全部执行完毕之后才会统一由浏览器重新渲染。所以,结果也就出来了,修改完isEdit值后,input还处于一个隐藏状态,而隐藏状态的input是不能有focus变化的。

nextTick的触发时机:

在同一事件循环中的数据变化后,DOM完成更新,立即执行nextTick(callback)内的回调。

所以上面的代码可以改为:

this.$nextTick((){
	this.$refs.inputTitle.focus()
})

应用场景:

需要在视图更新之后,基于新的视图进行操作

简单总结事件循环:

        同步代码执行 -> 查找异步队列,推入执行栈,执行callback1[事件循环1] ->查找异步队列,推入执行栈,执行callback2[事件循环2]... event-loop

即每个异步callback,最终都会形成自己独立的一个事件循环。

结合nextTick的由来,可以推出每个事件循环中,nextTick触发的时机:同一事件循环中的代码执行完毕 -> DOM 更新 -> nextTick callback触发

使用案例分析:

<template>
    <div>
        <ul>
            <li v-for="item in list1">{{item}}</li>
        </ul>
        <ul>
            <li v-for="item in list2">{{item}}</li>
        </ul>
        <ol>
            <li v-for="item in list3">{{item}}</li>
        </ol>
        <ol>
            <li v-for="item in list4">{{item}}</li>
        </ol>
        <ol>
            <li v-for="item in list5">{{item}}</li>
        </ol>
    </div>
</template>
<script type="text/javascript">
export default {
    data() {
        return {
            list1: [],
            list2: [],
            list3: [],
            list4: [],
            list5: []
        }
    },
    created() {
        this.composeList12()
        this.composeList34()
        this.composeList5()
        this.$nextTick(function() {
            // DOM 更新了
            console.log('finished test ' + new Date().toString())
            console.log(document.querySelectorAll('li').length)
        })
    },
    methods: {
        composeList12() {
            let me = this
            let count = 10000

            for (let i = 0; i < count; i++) {
                Vue.set(me.list1, i, 'I am a 测试信息~~啦啦啦' + i)
            }
            console.log('finished list1 ' + new Date().toString())

            for (let i = 0; i < count; i++) {
                Vue.set(me.list2, i, 'I am a 测试信息~~啦啦啦' + i)
            }
            console.log('finished list2 ' + new Date().toString())

            this.$nextTick(function() {
                // DOM 更新了
                console.log('finished tick1&2 ' + new Date().toString())
                console.log(document.querySelectorAll('li').length)
            })
        },
        composeList34() {
            let me = this
            let count = 10000

            for (let i = 0; i < count; i++) {
                Vue.set(me.list3, i, 'I am a 测试信息~~啦啦啦' + i)
            }
            console.log('finished list3 ' + new Date().toString())

            this.$nextTick(function() {
                // DOM 更新了
                console.log('finished tick3 ' + new Date().toString())
                console.log(document.querySelectorAll('li').length)
            })

            setTimeout(me.setTimeout1, 0)
        },
        setTimeout1() {
            let me = this
            let count = 10000

            for (let i = 0; i < count; i++) {
                Vue.set(me.list4, i, 'I am a 测试信息~~啦啦啦' + i)
            }
            console.log('finished list4 ' + new Date().toString())

            me.$nextTick(function() {
                // DOM 更新了
                console.log('finished tick4 ' + new Date().toString())
                console.log(document.querySelectorAll('li').length)
            })
        },
        composeList5() {
            let me = this
            let count = 10000

            this.$nextTick(function() {
                // DOM 更新了
                console.log('finished tick5-1 ' + new Date().toString())
                console.log(document.querySelectorAll('li').length)
            })

            setTimeout(me.setTimeout2, 0)
        },
        setTimeout2() {
            let me = this
            let count = 10000

            for (let i = 0; i < count; i++) {
                Vue.set(me.list5, i, 'I am a 测试信息~~啦啦啦' + i)
            }
            console.log('finished list5 ' + new Date().toString())

            me.$nextTick(function() {
                // DOM 更新了
                console.log('finished tick5 ' + new Date().toString())
                console.log(document.querySelectorAll('li').length)
            })
        }
    }
}
</script>

 用例设计

       用例1:通过list1、2、3验证,处在同步代码中的DOM更新情况及nextTick的触发时机;

  用例2:通过list3、list4验证,同步代码及异步代码中Dom更新及nextTick触发的区别;

  用例3:通过list4、list5对比验证,多个异步代码中nextTick触发的区别;

  用例4:通过在视图更新后获取DOM中<li>的数量,判断nextTick序列渲染的时间点。

代码分析

    函数执行步骤:

    事件循环1:

      step1: this.composeList12() -> update list1, update list2 -> 绑定tick’1&2’

      step2: this.composeList34() -> update list3, 设置异步1setTimeout1 -> 绑定tick’3’

      step3: this.composeList5() -> 绑定tick’5-1’ -> 设置异步2setTimeout2

      step4: 绑定tick’test’

    事件循环2:

      将setTimeout1的callback推入执行栈 -> update list4 -> 绑定tick’4’

    事件循环3:

      将setTimeout2的callback推入执行栈 -> update list5 -> 绑定tick’5’

推断输出消息

由于同一事件循环中的tick按执行顺序,因此消息输出为即:

          [同步环境]update list1 -> update list2 -> update list3 -> tick‘1&2’ -> tick‘3’ -> tick’5-1’ -> tick’test'

   [事件循环1]->update list4 -> tick’4’

    [事件循环2] ->update list5 -> tick’5’

实际运行结果如下图

自己补全

从用例1得出:

      a、在同一事件循环中,只有所有的数据更新完毕,才会调用nextTick;

      b、仅在同步执行环境数据完全更新完毕,DOM才开始渲染,页面才开始展现;

      c、在同一事件循环中,如果存在多个nextTick,将会按最初的执行顺序进行调用;

    从用例1+用例4得出:

      d、从同步执行环境中的四个tick对应的‘li’数量均为30000可看出,同一事件循环中,nextTick所在的视图是相同的;

    从用例2得出:

      e、只有同步环境执行完毕,DOM渲染完毕之后,才会处理异步callback

    从用例3得出:

      f、每个异步callback最后都会处在一个独立的事件循环中,对应自己独立的nextTick;

    从用例1结论中可得出:

      g、这个事件环境中的数据变化完成,在进行渲染[视图更新],可以避免DOM的频繁变动,从而避免了因此带来的浏览器卡顿,大幅度提升性能;

    从b可以得出:

      h、在首屏渲染、用户交互过程中,要巧用同步环境及异步环境;首屏展现的内容,尽量保证在同步环境中完成;其他内容,拆分到异步中,从而保证性能、体验。

  tips:

    1、可产生异步callback的有:promise(microtask queue)、setTimeout、MutationObserver、DOM事件、Ajax等;

    2、 vue DOM的视图更新实现,,使用到了ES6的Promise及HTML5的MutationObserver,当环境不支持时,使用setTimeout(fn, 0)替代。上述的三种方法,均为异步API。其中MutationObserver类似事件,又有所区别;事件是同步触发,其为异步触发,即DOM发生变化之后,不会立刻触发,等当前所有的DOM操作都结束后触发。

  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值