$nextTick

在项目中也有用到Vue中的nextTick,但是知其然而不知其所以然,故参考了很多资料进行了了解。其中关于nextTick的解析涉及到不少方面,很多了解的还不透彻,暂且根据自己的一些理解介绍下nextTick。

一、术语解释

在深入nextTick之前先认识几个术语

异步执行

众所周知,Javascript是一个单线程运行的脚本语言,以防止dom更新时冲突。而单线程意味着任务按顺序执行,后一个任务需要等待前一个任务的执行,这就可能出现长时间的阻塞。所以为了避免这种阻塞,我们需要一种非阻塞机制。这种非阻塞机制即异步执行机制,即通过将异步任务如ajax请求、setTimeou、dom交互等交给相应的异步模块去处理,主线程(即单线程)的效率大大提升,可以并行的去处理其他的操作。由此出现了任务队列和事件循环来协调主线程和异步模块之间的工作。

任务队列

当主线程中遇到异步任务时,将其交由对应的异步模块去进行处理,处理完毕后推入一个异步队列,主线程任务执行完毕会查询该异步队列,取出一个任务推入主线程,进行处理,此时的处理是处理异步任务完成后的回调,这个过程中的异步队列称为任务队列。

任务队列存在多个,同一任务队列内,按队列顺序被主线程取走;不同任务队列之间,存在着优先级,优先级高的优先获取(如用户I/O)

事件循环

1.主线程执行完毕,查询任务队列取出一个任务推入主线程处理回调。2.重复该动作。该过程称为事件循环。

简单事件循环机制示例图(帮助理解)

image

二、初识nextTick

官方解释

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

官方文档中提供了一个简单的应用例子,但并未指出原因以及用与不用的区别,在此对其官方实例进行改造:

模板

微信截图_20180627154130.png

Vue实例

image

点击前

image

点击后

image

可以看出msg2显示的内容是变换之后的,即在只有nextTick中的innerHtml更新完毕,由此可见在vue中,观测到数据变化了,dom不会立即变化,而是异步更新dom。此时再看nextTick官方解释是否可以这样理解(按照异步机制原理):vue实例数据变化,进入同步队列,而数据变化引发的dom更新作为一个异步任务进入异步队列进行更新,更新完成(称为:下次 DOM 更新循环结束)推入任务队列,同步队列任务执行完毕,去任务队列中取出异步任务执行回调(nextTick),在这个回调中才能保证dom更新完成,回调完成,继续上述步骤。而过程中:dom更新异步任务进入异步队列进行更新,更新完成推入任务队列,主线程去任务队列中取出异步任务执行称为一个tick。

三、深层次实例理解验证nextTick

验证设计

1.同一事件循环中数据更新与dom更新、nextTick的执行顺序

2.同一事件循环中不同nextTick的执行顺序、dom的更新顺序

3.利用setTimeout主动触发异步任务,异步任务与同步任务中数据更新、dom更新、nextTick的执行顺序

4.多个异步任务中数据更新、dom更新、nextTick的执行顺序

实例

<template>
    <div>
        <ul><li v-for="item in list1">{{item}}</li></ul>
        <ul><li v-for="item in list2">{{item}}</li></ul>
        <ul><li v-for="item in list3">{{item}}</li></ul>
        <ul><li v-for="item in list4">{{item}}</li></ul>
        <ul><li v-for="item in list5">{{item}}</li></ul>
        <ul><li v-for="item in list6">{{item}}</li></ul>
        <ul><li v-for="item in list7">{{item}}</li></ul>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                list1: [],
                list2: [],
                list3: [],
                list4: [],
                list5: [],
                list6: [],
                list7: [],
            };
        },
        created() {
            this.composeList12();
            this.composeList34();
            this.composeList5();
            this.composeList6();
            this.composeList7();
            this.$nextTick(() => {
                console.log(`finished test ${new Date().toString()}`);
                console.log(document.querySelectorAll('li').length);
            });
        },
        methods: {
            composeList12() {
                const count = 10000;

                for (let i = 0; i < count; i += 1) {
                    this.list1.push(`testList1----${i}`);
                }
                console.log(`changed list1 ${new Date().toString()}`);

                for (let i = 0; i < count; i += 1) {
                    this.list2.push(`testList2----${i}`);
                }
                console.log(`changed list2 ${new Date().toString()}`);

                this.$nextTick(() => {
                    console.log(`domChanged tick1&2 ${new Date().toString()}`);
                    console.log(document.querySelectorAll('li').length);
                });
            },
            composeList34() {
                const count = 10000;

                for (let i = 0; i < count; i += 1) {
                    this.list3.push(`testList3----${i}`);
                }
                console.log(`changed list3 ${new Date().toString()}`);

                this.$nextTick(() => {
                    // DOM 更新了
                    console.log(`domChanged tick3 ${new Date().toString()}`);
                    console.log(document.querySelectorAll('li').length);
                });

                setTimeout(this.setTimeout1, 0);
            },
            setTimeout1() {
                const count = 10000;

                for (let i = 0; i < count; i += 1) {
                    this.list4.push(`testList4----${i}`);
                }
                console.log(`changed list4 ${new Date().toString()}`);

                this.$nextTick(() => {
                    // DOM 更新了
                    console.log(`domChanged tick4 ${new Date().toString()}`);
                    console.log(document.querySelectorAll('li').length);
                });
            },
            composeList5() {
                const count = 10000;

                for (let i = 0; i < count; i += 1) {
                    this.list5.push(`testList5----${i}`);
                }
                console.log(`changed list5 ${new Date().toString()}`);

                this.$nextTick(() => {
                    // DOM 更新了
                    console.log(`domChanged tick5 ${new Date().toString()}`);
                    console.log(document.querySelectorAll('li').length);
                });
            },
            composeList6() {
                const count = 10000;

                this.$nextTick(() => {
                    // DOM 更新了
                    console.log(`finished tick6-1 ${new Date().toString()}`);
                    console.log(document.querySelectorAll('li').length);
                });

                setTimeout(this.setTimeout2, 0);
            },
            setTimeout2() {
                const count = 10000;

                for (let i = 0; i < count; i += 1) {
                    this.list6.push(`testList6----${i}`);
                }
                console.log(`changed list6 ${new Date().toString()}`);

                this.$nextTick(() => {
                    // DOM 更新了
                    console.log(`domChanged tick6 ${new Date().toString()}`);
                    console.log(document.querySelectorAll('li').length);
                });
            },
            composeList7() {
                const count = 10000;

                for (let i = 0; i < count; i += 1) {
                    this.list7.push(`testList7----${i}`);
                }
                console.log(`changed list7 ${new Date().toString()}`);

                this.$nextTick(() => {
                    // DOM 更新了
                    console.log(`domChanged tick7 ${new Date().toString()}`);
                    console.log(document.querySelectorAll('li').length);
                });
            },
        },
    };
</script>

运行结果

image

从结果中可以看出事件循环步骤如下:

同步环境:update list1 -> update list2 -> update list3->setTimeout1进入异步模块-> update list5->setTimeout2进入异步模块-> update list7 ->[事件循环1]异步dom更新 ->依次执行tick: tick‘1&2’ -> tick‘3’ -> tick‘5’ -> tick’6-1’ -> tick‘7’ -> tick’test'->[事件循环2]update list4 -> tick’4’ ->[事件循环3]update list6 -> tick’6’

结论

1.在同一事件循环中,只有所有的数据更新完毕,异步dom更新完成,才会调用nextTick--->官方解释:观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,并在缓冲时去除重复数据变化,可以避免DOM的频繁变动,从而避免了因此带来的浏览器卡顿,大幅度提升性能。

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

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

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

四、应用场景

1.在Vue生命周期的created()钩子函数进行DOM操作时一定要将操作放在nextTick()的回调函数中,因为在created生命周期时初始dom结构还未挂载到实例上且未渲染到页面上。与之对应的mount()钩子函数,该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。

2.v-if/v-show变化后需要获取dom结构时

总体来说就是nextTick作为vue提供的一个钩子函数,在需要使用随数据改变而改变的DOM结构的时候,可以将操作放在Vue.nextTick()的回调函数中保证必然能够取到dom结构,并且在渲染、用户交互过程中,要巧用同步环境及异步环境,首页展现的内容,尽量保证在同步环境中完成,其他内容,拆分到异步中,从而保证性能和体验。
--- 一直在默默探索的前端小白好奇宝宝。

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

推荐阅读更多精彩内容