转载自https://segmentfault.com/a/1190000012861862

概览

官方文档说明:

  • 用法:

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

疑问:

  1. DOM 更新循环是指什么?
  2. 下次更新循环是什么时候?
  3. 修改数据之后使用,是加快了数据更新进度吗?
  4. 在什么情况下要用到?

原理

异步说明

Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。

在 Vue 的文档中,说明 Vue 是异步执行 DOM 更新的。关于异步的解析,可以查看阮一峰老师的这篇文章。截取关键部分如下:

具体来说,异步执行的运行机制如下。

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

下图就是主线程和任务队列的示意图。

事件循环说明

简单来说,Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

知乎上的例子:

  1. //改变数据
  2. vm.message = 'changed'
  3. //想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新
  4. console.log(vm.$el.textContent) // 并不会得到'changed'
  5. //这样可以,nextTick里面的代码会在DOM更新后执行
  6. Vue.nextTick(function(){
  7. console.log(vm.$el.textContent) //可以得到'changed'
  8. })

图解:

事件循环:

第一个 tick(图例中第一个步骤,即'本次更新循环'):

  1. 首先修改数据,这是同步任务。同一事件循环的所有的同步任务都在主线程上执行,形成一个执行栈,此时还未涉及 DOM 。
  2. Vue 开启一个异步队列,并缓冲在此事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。

第二个 tick(图例中第二个步骤,即'下次更新循环'):

同步任务执行完毕,开始执行异步 watcher 队列的任务,更新 DOM 。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel 方法,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。

第三个 tick(图例中第三个步骤):

此时就是文档所说的

下次 DOM 更新循环结束之后

此时通过 Vue.nextTick 获取到改变后的 DOM 。通过 setTimeout(fn, 0) 也可以同样获取到。


简单总结事件循环:

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

总之,异步是单独的一个tick,不会和同步在一个 tick 里发生,也是 DOM 不会马上改变的原因。

对于事件循环,可以在这里查看更详细的内容: https://segmentfault.com/a/11...

用途

应用场景:需要在视图更新之后,基于新的视图进行操作。

created、mounted

需要注意的是,在 created 和 mounted 阶段,如果需要操作渲染后的试图,也要使用 nextTick 方法。

官方文档说明:

注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted

  1. mounted: function () {
  2. this.$nextTick(function () {
  3. // Code that will run only after the
  4. // entire view has been rendered
  5. })
  6. }

其他应用场景

其他应用场景如下三例:

例子1:

点击按钮显示原本以 v-show = false 隐藏起来的输入框,并获取焦点。

  1. showsou(){
  2. this.showit = true //修改 v-show
  3. document.getElementById("keywords").focus() //在第一个 tick 里,获取不到输入框,自然也获取不到焦点
  4. }

修改为:

  1. showsou(){
  2. this.showit = true
  3. this.$nextTick(function () {
  4. // DOM 更新了
  5. document.getElementById("keywords").focus()
  6. })
  7. }

例子2:

点击获取元素宽度。

  1. <div id="app">
  2. <p ref="myWidth" v-if="showMe">{{ message }}</p>
  3. <button @click="getMyWidth">获取p元素宽度</button>
  4. </div>
  5. getMyWidth() {
  6. this.showMe = true;
  7. //this.message = this.$refs.myWidth.offsetWidth;
  8. //报错 TypeError: this.$refs.myWidth is undefined
  9. this.$nextTick(()=>{
  10. //dom元素更新后执行,此时能拿到p元素的属性
  11. this.message = this.$refs.myWidth.offsetWidth;
  12. })
  13. }

例子3:

使用 swiper 插件通过 ajax 请求图片后的滑动问题。

实例理解 nextTick 应用

下面的例子来自 https://www.cnblogs.com/hity-..., 稍有改动。各位可以复制运行一遍,加深理解。

  1. <template>
  2. <div>
  3. <ul>
  4. <li class="example" v-for="item in list1">{{item}}</li>
  5. </ul>
  6. <ul>
  7. <li class="example" v-for="item in list2">{{item}}</li>
  8. </ul>
  9. <ol>
  10. <li class="example" v-for="item in list3">{{item}}</li>
  11. </ol>
  12. <ol>
  13. <li class="example" v-for="item in list4">{{item}}</li>
  14. </ol>
  15. <ol>
  16. <li class="example" v-for="item in list5">{{item}}</li>
  17. </ol>
  18. </div>
  19. </template>
  20. <script type="text/javascript">
  21. export default {
  22. data() {
  23. return {
  24. list1: [],
  25. list2: [],
  26. list3: [],
  27. list4: [],
  28. list5: []
  29. }
  30. },
  31. created() {
  32. this.composeList12()
  33. this.composeList34()
  34. this.composeList5()
  35. this.$nextTick(function() {
  36. // DOM 更新了
  37. console.log('finished test ' + new Date().toString(),document.querySelectorAll('.example').length)
  38. })
  39. },
  40. methods: {
  41. composeList12() {
  42. let me = this
  43. let count = 10000
  44. for (let i = 0; i < count; i++) {
  45. this.$set(me.list1, i, 'I am a 测试信息~~啦啦啦' + i)
  46. }
  47. console.log('finished list1 ' + new Date().toString(),document.querySelectorAll('.example').length)
  48. for (let i = 0; i < count; i++) {
  49. this.$set(me.list2, i, 'I am a 测试信息~~啦啦啦' + i)
  50. }
  51. console.log('finished list2 ' + new Date().toString(),document.querySelectorAll('.example').length)
  52. this.$nextTick(function() {
  53. // DOM 更新了
  54. console.log('finished tick1&2 ' + new Date().toString(),document.querySelectorAll('.example').length)
  55. })
  56. },
  57. composeList34() {
  58. let me = this
  59. let count = 10000
  60. for (let i = 0; i < count; i++) {
  61. this.$set(me.list3, i, 'I am a 测试信息~~啦啦啦' + i)
  62. }
  63. console.log('finished list3 ' + new Date().toString(),document.querySelectorAll('.example').length)
  64. this.$nextTick(function() {
  65. // DOM 更新了
  66. console.log('finished tick3 ' + new Date().toString(),document.querySelectorAll('.example').length)
  67. })
  68. setTimeout(me.setTimeout1, 0)
  69. },
  70. setTimeout1() {
  71. let me = this
  72. let count = 10000
  73. for (let i = 0; i < count; i++) {
  74. this.$set(me.list4, i, 'I am a 测试信息~~啦啦啦' + i)
  75. }
  76. console.log('finished list4 ' + new Date().toString(),document.querySelectorAll('.example').length)
  77. me.$nextTick(function() {
  78. // DOM 更新了
  79. console.log('finished tick4 ' + new Date().toString(),document.querySelectorAll('.example').length)
  80. })
  81. },
  82. composeList5() {
  83. let me = this
  84. let count = 10000
  85. this.$nextTick(function() {
  86. // DOM 更新了
  87. console.log('finished tick5-1 ' + new Date().toString(),document.querySelectorAll('.example').length)
  88. })
  89. setTimeout(me.setTimeout2, 0)
  90. },
  91. setTimeout2() {
  92. let me = this
  93. let count = 10000
  94. for (let i = 0; i < count; i++) {
  95. this.$set(me.list5, i, 'I am a 测试信息~~啦啦啦' + i)
  96. }
  97. console.log('finished list5 ' + new Date().toString(),document.querySelectorAll('.example').length)
  98. me.$nextTick(function() {
  99. // DOM 更新了
  100. console.log('finished tick5 ' + new Date().toString(),document.querySelectorAll('.example').length)
  101. })
  102. }
  103. }
  104. }
  105. </script>

结果:

参考文章

vue nextTick深入理解-vue性能优化、DOM更新时机、事件循环机制;
JavaScript 运行机制详解:再谈Event Loop
知乎:vue.js$nextTick的一个问题
JS 事件循环机制 - 任务队列、web API、JS主线程的相互协同

Vue.nextTick 的原理和用途的更多相关文章

  1. 总结了一下 Vue.nextTick() 的原理和用途

    对于 Vue.nextTick 方法,自己有些疑惑.在查询了各种资料后,总结了一下其原理和用途,如有错误,请不吝赐教. 概览 官方文档说明: 用法: 在下次 DOM 更新循环结束之后执行延迟回调.在修 ...

  2. 【转载】Vue.nextTick 的原理和用途

    对于 Vue.nextTick 方法,自己有些疑惑.在查询了各种资料后,总结了一下其原理和用途,如有错误,请不吝赐教. 概览 官方文档说明: 用法: 在下次 DOM 更新循环结束之后执行延迟回调.在修 ...

  3. 全面解析Vue.nextTick实现原理

    vue中有一个较为特殊的API,nextTick.根据官方文档的解释,它可以在DOM更新完毕之后执行一个回调,用法如下: // 修改数据 vm.msg = 'Hello' // DOM 还没有更新 V ...

  4. vue.$nextTick实现原理

    源码: const callbacks = [] let pending = false function flushCallbacks () { pending = false const copi ...

  5. vue2.0 正确理解Vue.nextTick()的用途

    什么是Vue.nextTick() 官方文档解释如下: 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 获取更新后的DOM,言外之意就是DOM更新 ...

  6. Vue nextTick 机制

    背景 我们先来看一段Vue的执行代码: export default { data () { return { msg: 0 } }, mounted () { this.msg = 1 this.m ...

  7. 深度解析 Vue 响应式原理

    深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进 ...

  8. vue.nextTick()方法的使用详解

    什么是Vue.nextTick()??   定义:在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 所以就衍生出了这个获取更新后的DOM的Vue方法 ...

  9. Vue源码阅读一:说说vue.nextTick实现

    用法: 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 疑惑: 怎么实现的延迟回调 原理: JavaScript语言的一大特点就是单线程,同一个时 ...

随机推荐

  1. 在mac中,npm安装或者卸载失败,提示没有权限

    在终端输入 sudo chown -R $USER /usr/local 输入开机密码

  2. eclipse中解决update maven之后jre被改成1.5的问题

    1.在项目的pom.xml中加入下面的代码就能解决(加入插件) <build> <plugins> <plugin> <groupId>org.apac ...

  3. Scrum立会报告+燃尽图 02

    本次作业要求参见:https://edu.cnblogs.com/campus/nenu/2019fall/homework/9912 一.小组情况组长:贺敬文组员:彭思雨 王志文 位军营 徐丽君队名 ...

  4. leetcode 64. 最小路径和Minimum Path Sum

    很典型的动态规划题目 C++解法一:空间复杂度n2 class Solution { public: int minPathSum(vector<vector<int>>&am ...

  5. nodejs之express静态路由、ejs

    1.静态路由与ejs使用 /** *1.安装ejs npm install ejs --save-dev * *2.express 里面使用ejs ,安装以后就可以用,不需要引入 * *3.配置exp ...

  6. ffmpeg 视频过度滤镜 gltransition

    ffmpeg 视频过度滤镜 gltransition 上次随笔中提到的 ffmpeg-concat 可以处理视频过度,但是缺点是临时文件超大. 经过查找 ffmpeg 还有 gltransition ...

  7. SAS数据挖掘实战篇【六】

    SAS数据挖掘实战篇[六] 6.3  决策树 决策树主要用来描述将数据划分为不同组的规则.第一条规则首先将整个数据集划分为不同大小的 子集,然后将另外的规则应用在子数据集中,数据集不同相应的规则也不同 ...

  8. VirtualBox-5.2.8-121009-Win,虚拟机指令ifconfig不显示ip解决方法

  9. Swift 发送邮件和附件

    public function send($filename, array $render = [],$subject = '审核通知') { // Create the Transport $tra ...

  10. centos7 忘记root密码,如何进入单用户模式。

    init方法 1.centos7的grub2界面会有两个入口,正常系统入口和救援模式: 2.修改grub2引导 在正常系统入口上按下"e",会进入edit模式,搜寻ro那一行,以l ...