vue nextTick深入理解-vue性能优化、DOM更新时机、事件循环机制

一、定义[nextTick、事件循环]

  nextTick的由来:

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

  nextTick的触发时机:

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

  应用场景:

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

  以上出现了事件循环的概念,其涉及到JS的运行机制,包括主线程的执行栈、异步队列、异步API、事件循环的协作,此处不展开之后再总结。大致理解:主线程完成同步环境执行,查询任务队列,提取队首的任务,放入主线程中执行;执行完毕,再重复该操作,该过程称为事件循环。而主线程的每次读取任务队列操作,是一个事件循环的开始。异步callback不可能处在同一事件循环中。

  简单总结事件循环:

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

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

  结合nextTick的由来,可以推出每个事件循环中,nextTick触发的时机:

    同一事件循环中的代码执行完毕 -> DOM 更新 -> nextTick callback触发

  tips:本文的任务队列、消息队列、异步队列指同一个东西,均指macrotask queue。

       事件循环详解:http://www.cnblogs.com/hity-tt/p/6733062.html

二、实例理解nextTick的使用,并给出在页面渲染上的优化巧用

  (tips:代码的正确阅读方式:看template组成、跳过script代码、看代码后面的用例设计、看之后的代码分析、同时结合回头结合script代码理解)

<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>

  2.1、用例设计

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

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

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

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

  2.2、代码分析

    函数执行步骤:

      事件循环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’

  2.3、推断输出消息

    由于同一事件循环中的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’

  2.4、实际运行结果如下图

     

    该demo中,设置了5个size为10000的数组,从而能从时间及消息输出两个维度来了解nextTick的执行情况。另外,额外增加了一个参数,即更新后的视图中<li>的数量,从这个数量,可以考察出同一事件循环中的nextTick执行情况。由运行结果图可以看出实际的输出与推导的输出结果相符合。

  2.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操作都结束后触发。关于异步API、事件循环将在以后补充。

    事件循环、任务队列详解:http://www.cnblogs.com/hity-tt/p/6733062.html

原文:https://www.cnblogs.com/hity-tt/p/6729118.html#4099338

原文地址:https://www.cnblogs.com/kelly-sunshine/p/10795083.html

时间: 2024-08-21 09:04:34

vue nextTick深入理解-vue性能优化、DOM更新时机、事件循环机制的相关文章

js的事件循环机制:同步与异步任务(setTimeout,setInterval)宏任务,微任务(Promise,process.nextTick)

javascript是单线程,一切javascript版的"多线程"都是用单线程模拟出来的,通过事件循环(event loop)实现的异步. javascript事件循环 事件循环中的同步任务,异步任务: 同步和异步任务在不同的执行"场所",同步的进入主线程,异步的进入Event Table执行并注册函数. 当指定的异步事情完成时,Event Table会将这个函数移入Event Queue. 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,推

对javascript EventLoop事件循环机制不一样的理解

前置知识点: 浏览器原理,浏览器内核5种线程及协作,JS引擎单线程设计推荐阅读: 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理 [FE]浏览器渲染引擎「内核」 js异步编程,Promise实现推荐阅读: Javascript异步编程的4种方法 前端面试必考题Promise的源码解析 堆.栈.队列.执行栈.任务.微任务.事件循环机制??推荐阅读: JavaScript异步编程-基础篇 彻底搞懂浏览器Event-loop 这一次,彻底弄懂 JavaScript 执行机制 一次弄懂Even

深入理解JavaScript事件循环机制

前言 众所周知,JavaScript 是一门单线程语言,虽然在 html5 中提出了 Web-Worker ,但这并未改变 JavaScript 是单线程这一核心.可看HTML规范中的这段话: To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. Ther

vue nexttick的理解和使用场景

应用场景 需要在视图更新之后,基于新的视图进行操作 文档说明 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM nextTick原理 1.异步说明 Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新 2.事件循环说明 简单来说,Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新. created.mounted 在 created 和 mounted

【Vue】彻底理解Vue中render函数与template的区别

一.render函数与template对比 VUE一般使用template来创建HTML,然后在有的时候,我们需要使用javascript来创建html,这时候我们需要使用render函数. 以下我们来做一个需求跟根据level等级来编写对应等级的标题 template解析 <body>   <divid="app">       <h-titlelevel=1>           <p>li</p>       </

ListView深入理解:性能优化1

一:列表显示三要素: 1.ListView:用于显示列表的view: 2.Adapter:  将数据映射到ListView: 3.Data:     具体的将要被映射的字符串.图片或其他基本组件: 二:优化原理: 1.只创建必要的view对象: ListView加载数据的方式: 首先调用getCount()函数,根据返回值得到listView的长度: 然后根据这个长度,调用getView()获取listView的每一项item所对应的view. 如果每一个item都去创建新的对象,必然会占据过多

Android性能优化之Listview(ViewHolder重用机制)

相信大家在很多时候都会用到ListView这个控件,因为确实是用的很多很多,但是有木有遇到过当数据很多很多的时候,往下滑ListView时有时候会卡顿,这就需要我们来优化它了. ListView优化主要有下面几个方面: 1.convertView重用 2.ViewHolder的子View复用 3.缓存数据复用 一.convertView重用 首先讲下ListView的原理:ListView中的每一个Item显示都需要Adapter调用一次getView()的方法,这个方法会传入一个convert

JavaScript性能优化 DOM编程

最近在研读<高性能JavaScript>,在此做些简单记录.示例代码可在此处查看到. 一.DOM 1)DOM和JavaScript 文档对象模型(DOM)是一个独立于语言的,用于操作XML和HTML文档的程序接口(API). 浏览器通常会把DOM和JavaScript独立实现.例如Chrome中使用Webkit的WebCore库渲染页面,用V8作为JavaScript引擎. 访问DOM天生就慢,将DOM和JavaScript比喻为两个岛屿,两处同行要收过桥费,ECMAScript访问DOM的次

前端性能优化(更新中...)

今天网站上线自己搜集总结了一些前端的优化1.精灵图 多个图片拼成一个图片,然后通过CSS来控制在什么地方具体显示这整张图片的什么位置(毕竟流量 都是钱啊) 2.css选择器优化 3. js改变样式直接操作类名 4. js直接操作dom节点 5.正则匹配选择器6.js获取元素优化 7.内存溢出 一般在递归运行时,会产生内存溢出,造成在运行递归时性能大幅度下降,在运行结束后内存 会被系统回收,所以在运行递归时需要用对象将值保存,在每次递归运算时检测,如果存在则直接返 回,不存在则添加,这样就可以解决