nextTick、事件循环

一、定义[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代码理解)

   

       
               
  • {{item}}
  •        
       
               
  • {{item}}
  •        
       
               
  1. {{item}}
  2.        
       
               
  1. {{item}}
  2.        
       
               
  1. {{item}}
  2.        
   
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 = 10000for(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 = 10000for(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 = 10000for(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 = 10000this.$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 = 10000for(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)            })        }    }}

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 

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容