1. 对于 Vue.nextTick 方法,自己有些疑惑。在查询了各种资料后,总结了一下其原理和用途,如有错误,请不吝赐教。

概览

官方文档说明:

  • 用法:

在下次 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.  
  6. getMyWidth() {
  7. this.showMe = true;
  8. //this.message = this.$refs.myWidth.offsetWidth;
  9. //报错 TypeError: this.$refs.myWidth is undefined
  10. this.$nextTick(()=>{
  11. //dom元素更新后执行,此时能拿到p元素的属性
  12. this.message = this.$refs.myWidth.offsetWidth;
  13. })
  14. }

例子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.  
  45. for (let i = 0; i < count; i++) {
  46. this.$set(me.list1, i, 'I am a 测试信息~~啦啦啦' + i)
  47. }
  48. console.log('finished list1 ' + new Date().toString(),document.querySelectorAll('.example').length)
  49.  
  50. for (let i = 0; i < count; i++) {
  51. this.$set(me.list2, i, 'I am a 测试信息~~啦啦啦' + i)
  52. }
  53. console.log('finished list2 ' + new Date().toString(),document.querySelectorAll('.example').length)
  54.  
  55. this.$nextTick(function() {
  56. // DOM 更新了
  57. console.log('finished tick1&2 ' + new Date().toString(),document.querySelectorAll('.example').length)
  58. })
  59. },
  60. composeList34() {
  61. let me = this
  62. let count = 10000
  63.  
  64. for (let i = 0; i < count; i++) {
  65. this.$set(me.list3, i, 'I am a 测试信息~~啦啦啦' + i)
  66. }
  67. console.log('finished list3 ' + new Date().toString(),document.querySelectorAll('.example').length)
  68.  
  69. this.$nextTick(function() {
  70. // DOM 更新了
  71. console.log('finished tick3 ' + new Date().toString(),document.querySelectorAll('.example').length)
  72. })
  73.  
  74. setTimeout(me.setTimeout1, 0)
  75. },
  76. setTimeout1() {
  77. let me = this
  78. let count = 10000
  79.  
  80. for (let i = 0; i < count; i++) {
  81. this.$set(me.list4, i, 'I am a 测试信息~~啦啦啦' + i)
  82. }
  83. console.log('finished list4 ' + new Date().toString(),document.querySelectorAll('.example').length)
  84.  
  85. me.$nextTick(function() {
  86. // DOM 更新了
  87. console.log('finished tick4 ' + new Date().toString(),document.querySelectorAll('.example').length)
  88. })
  89. },
  90. composeList5() {
  91. let me = this
  92. let count = 10000
  93.  
  94. this.$nextTick(function() {
  95. // DOM 更新了
  96. console.log('finished tick5-1 ' + new Date().toString(),document.querySelectorAll('.example').length)
  97. })
  98.  
  99. setTimeout(me.setTimeout2, 0)
  100. },
  101. setTimeout2() {
  102. let me = this
  103. let count = 10000
  104.  
  105. for (let i = 0; i < count; i++) {
  106. this.$set(me.list5, i, 'I am a 测试信息~~啦啦啦' + i)
  107. }
  108. console.log('finished list5 ' + new Date().toString(),document.querySelectorAll('.example').length)
  109.  
  110. me.$nextTick(function() {
  111. // DOM 更新了
  112. console.log('finished tick5 ' + new Date().toString(),document.querySelectorAll('.example').length)
  113. })
  114. }
  115. }
  116. }
  117. </script>

结果:

【转载】Vue.nextTick 的原理和用途的更多相关文章

  1. Vue.nextTick 的原理和用途

    转载自https://segmentfault.com/a/1190000012861862 概览 官方文档说明: 用法: 在下次 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.js面试题一

    转载自:https://www.cnblogs.com/aimeeblogs/p/9501490.html 如有侵权 联系删除 Vue.js面试题整理 一.什么是MVVM? MVVM是Model-Vi ...

  7. Vue nextTick 机制

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

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

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

  9. N-gram的原理、用途和研究

    N-gram的原理.用途和研究 N-gram的基本原理 转自:http://blog.sciencenet.cn/blog-713101-797384.html N-gram是计算机语言学和概率论范畴 ...

随机推荐

  1. Cys_Control(四) MTabControl

    一.查看TabControl原样式 <ControlTemplate TargetType="{x:Type TabControl}"> <Grid x:Name ...

  2. ElementUI el-date-picker 限制选中日期前后30天,大于当天不可选

    <template> <el-date-picker v-model="date" type="daterange" range-separa ...

  3. App自动化《元素定位方式、元素操作、混合应用、分层设计、代码方式执行Pytest 命令》

    坚持原创输出,点击蓝字关注我吧 作者:清菡 博客:oschina.云+社区.知乎等各大平台都有. 目录 一.App 元素定位方式 二.元素操作 三.测试混合应用 四.以代码的方式执行 Pytest 命 ...

  4. 区块链学习7:超级账本项目Fabric中的背书、背书节点、背书策略、背书签名

    ☞ ░ 前往老猿Python博文目录 ░ 在Hyperledger Fabric区块链中,有背书节点进行背书,Hyperledger Fabric 使用背书策略来定义哪些节点需要执行交易. Hyper ...

  5. OpenCV阈值处理函数threshold处理32位彩色图像的案例

    ☞ ░ 前往老猿Python博文目录 ░ 一.概述 openCV图像的阈值处理又称为二值化,之所以称为二值化,是它可以将一幅图转换为感兴趣的部分(前景)和不感兴趣的部分(背景).转换时,通常将某个值( ...

  6. moviepy音视频剪辑:AudioClip帧处理时报TypeError: only size-1 arrays can be converted to Python scalar错

    ☞ ░ 前往老猿Python博文目录 ░ 一.环境 操作系统:win7 64位 moviepy:1.0.3 numpy:1.19.0 Python:3.7.2 二.应用代码及报错信息 程序代码 if ...

  7. 转:【Python3网络爬虫开发实战】6.4-分析Ajax爬取今日头条街拍美图

    [摘要] 本节中,我们以今日头条为例来尝试通过分析Ajax请求来抓取网页数据的方法.这次要抓取的目标是今日头条的街拍美图,抓取完成之后,将每组图片分文件夹下载到本地并保存下来. 1. 准备工作 在本节 ...

  8. PyQt(Python+Qt)学习随笔:Qt Designer中主窗口对象dockNestingEnabled属性

    dockNestingEnabled 属性是确认主窗口的浮动部件(dock widget)是否允许嵌套的一个属性. 如果此属性为False,则浮动部件停靠区域只能包含一个浮动部件(水平或垂直).如果此 ...

  9. PyQt(Python+Qt)学习随笔:部件的inputMethodHints属性

    inputMethodHints属性只对输入部件有效,输入法使用它来检索有关输入法应如何操作的提示,例如,如果设置了只允许输入数字的标志,则输入法可能会更改其可视组件,以反映只能输入数字.相关取值及含 ...

  10. scrapy爬虫登录edusrc查看漏洞列表

    scrapy登录界面的难点在于登录时候的验证码,我们通过使用scrapy.FormRequest向目标网站提交数据(表单提交),同时将验证码显示在本地,手动输入,进而登录. 验证码是类似于这种的,才可 ...