Vue.nextTick浅析

Vue的特点之一就是响应式,但数据更新时,DOM并不会立即更新。当我们有一个业务场景,需要在DOM更新之后再执行一段代码时,可以借助nextTick实现。以下是来自官方文档的介绍:

将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。

具体的使用场景和底层代码实现在后面的段落说明和解释。

用途

Vue.nextTick( [callback, context] ) 与 vm.$nextTick( [callback] )

前者是全局方法,可以显式指定执行上下文,而后者是实例方法,执行时自动绑定this到当前实例上。

以下是一个nextTick使用例子:

```<div id="app">
<button @click="add">add</button>
{{count}}
<ul ref="ul">
<li v-for="item in list">
{{item}}
</li>
</ul>
</div>
```


new Vue({
el: '#app',
data: {
count: 0,
list: []
},
methods:{
add() {
this.count += 1
this.list.push(1)
let li = this.$refs.ul.querySelectorAll('li')
li.forEach(item=&gt;{
item.style.color = 'red';
})
}
}
})

以上的代码,期望在每次新增一个列表项时都使得列表项的字体是红色的,但实际上新增的列表项字体仍是黑色的。尽管data已经更新,但新增的li元素并不立即插入到DOM中。如果希望在DOM更新后再更新样式,可以在nextTick的回调中执行更新样式的操作。


new Vue({
el: '#app',
data: {
count: 0,
list: []
},
methods:{
add() {
this.count += 1
this.list.push(1)
this.$nextTick(()=&gt;{
let li = this.$refs.ul.querySelectorAll('li')
li.forEach(item=&gt;{
item.style.color = 'red';
})
})
}
}
})

解释

数据更新时,并不会立即更新DOM。如果在更新数据之后的代码执行另一段代码,有可能达不到预想效果。将视图更新后的操作放在nextTick的回调中执行,其底层通过微任务的方式执行回调,可以保证DOM更新后才执行代码。

源码

在/src/core/instance/index.js,执行方法renderMixin(Vue)为Vue.prototype添加了$nextTick方法。实际在Vue.prototype.$nextTick中,执行了nextTick(fn, this),这也是vm.$nextTick( [callback] )自动绑定this到执行上下文的原因。

nextTick函数在/scr/core/util/next-tick.js声明。在next-tick.js内,使用数组callbacks保存回调函数,pending表示当前状态,使用函数flushCallbacks来执行回调队列。在该方法内,先通过slice(0)保存了回调队列的一个副本,通过设置callbacks.length = 0清空回调队列,最后使用循环执行在副本里的所有函数。


const callbacks = []
let pending = false function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i &lt; copies.length; i++) {
copies[i]()
}
}

接着定义函数marcoTimerFunc、microTimerFunc。

先判断是否支持setImmediate,如果支持,使用setImmediate执行回调队列;如果不支持,判断是否支持MessageChannel,支持时,在port1监听message,将flushCallbacks作为回调;如果仍不支持MessageChannel,使用setTimeout(flushCallbacks, 0)执行回调队列。不管使用哪种方式,macroTimerFunc最终目的都是在一个宏任务里执行回调队列。


if (typeof setImmediate !== 'undefined' &amp;&amp; isNative(setImmediate)) {
macroTimerFunc = () =&gt; {
setImmediate(flushCallbacks)
}
} else if (typeof MessageChannel !== 'undefined' &amp;&amp; (
isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () =&gt; {
port.postMessage(1)
}
} else {
/* istanbul ignore next */
macroTimerFunc = () =&gt; {
setTimeout(flushCallbacks, 0)
}
}

然后判断是否支持Promise,支持时,新建一个状态为resolved的Promise对象,并在then回调里执行回调队列,如此,便在一个微任务中执行回调,在IOS的UIWebViews组件中,尽管能创建一个微任务,但这个队列并不会执行,除非浏览器需要执行其他任务;所以使用setTimeout添加一个不执行任何操作的回调,使得微任务队列被执行。如果不支持Promise,使用降级方案,将microTimerFunc指向macroTimerFunc。


if (typeof Promise !== 'undefined' &amp;&amp; isNative(Promise)) {
const p = Promise.resolve()
microTimerFunc = () =&gt; {
p.then(flushCallbacks)
// in problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
} else {
// fallback to macro
microTimerFunc = macroTimerFunc
}

在函数nextTick内,先将函数cb使用箭头函数包装起来并添加到回调队列callbacks。接着判断当前是否正在执行回调,如果不是,将pengding设置为真。判断回调执行是宏任务还是微任务,分别通过marcoTimerFunc、microTimerFunc来触发回调队列。最后返回一个Promise实例以支持链式调用。


export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() =&gt; {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
if (useMacroTask) {
macroTimerFunc()
} else {
microTimerFunc()
}
}
// $flow-disable-line
if (!cb &amp;&amp; typeof Promise !== 'undefined') {
return new Promise(resolve =&gt; {
_resolve = resolve
})
}
}

而全局方法Vue.nextTick在/src/core/global-api/index.js中声明,是对函数nextTick的引用,所以使用时可以显示指定执行上下文。


Vue.nextTick = nextTick

小结

本文关于nextTick的使用场景和源码做了简单的介绍,如果想深入了解这部分的知识,可以去了解一下微任务mircotask和宏任务marcotask。

来源:https://segmentfault.com/a/1190000016495892

Vue.nextTick浅析的更多相关文章

  1. Vue中的nextTick()浅析

    引言 在开发过程中,我们经常遇到这样的问题:我明明已经更新了数据,为什么当我获取某个节点的数据时,却还是更新前的数据? 一,浅析 为什么会这样呢?带着这个疑问先往下看. 先看一个小的例子: <d ...

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

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

  3. Vue nextTick 机制

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

  4. vue nextTick使用

    Vue nextTick使用 vue生命周期 原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue. ...

  5. Vue.$nextTick

    `Vue.nextTick(callback)`,当数据发生变化,更新后执行回调. `Vue.$nextTick(callback)`,当dom发生变化,更新后执行的回调

  6. Vue 之 Vue.nextTick()

    异步更新队列 可能你还没有注意到,Vue 异步执行 DOM 更新.只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变.如果同一个 watcher 被多次触发,只会一次 ...

  7. Vue.nextTick和Vue.$nextTick

    `Vue.nextTick(callback)`,当数据发生变化,更新后执行回调. `Vue.$nextTick(callback)`,当dom发生变化,更新后执行的回调. 参考原文:http://w ...

  8. 我理解的关于Vue.nextTick()的正确使用

    什么是Vue.nextTick() 官方文档解释如下: 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 我理解的官方文档的这句话的侧重点在最后那半 ...

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

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

随机推荐

  1. IDC装机检查思路

    交换机网口 网线 配线架 服务器网口灯 系统网卡驱动

  2. Linux培训教程 linux磁盘分区详解

    在学习 Linux 的过程中,安装 Linux 是每一个初学者的第一个门槛.在这个过程中间,最大的困惑莫过于给硬盘进行分区.虽然,现在各种发行版本的 Linux 已经提供了友好的图形交互界面,但是很多 ...

  3. SpringBoot2.2发行版新特性

    Spring Framework升级 SpringBoot2.2的底层Spring Framework版本升级为5.2. JMX默认禁用 默认情况下不再启用JMX. 可以使用配置属性spring.jm ...

  4. java中的浅拷贝和深拷贝

    复制 将一个对象的引用复制给另一个对象,一共有三种方式.第一种方式是直接赋值,第二种方式是浅复制,第三种方式是深复制. 1.直接赋值 在Java中,A a1 = a2,这实际上复制的是引用,也就是说 ...

  5. 取值函数(getter)和存值函数(setter)

    todo get和set关键字

  6. sqli-labs(6)

    双注入GET双引号字符型注入 0x01(这里思路和05一样只是单引号变成了双引号) 首先我们看见这个网站 又是不显示值的 所以想到用报错的信息来得出你想要的结果 有了思路之后来试试一下吧 先填入单引号 ...

  7. #20175201 实现mypwd

    实验要求 学习pwd命令 研究pwd实现需要的系统调用(man -k; grep),写出伪代码 实现mypwd 测试mypwd 了解pwd命令   pwd是"Print Working Di ...

  8. Python-Django WebAPi基本使用方法

    目的 利用django搭建一个WebApi,实现数据库表的增删查改. 数据传输基于Http协议,数据格式:JSON ORM方式进行数据库表的CRUD(增删查改) 开发步骤 使用pycharm专业版搭建 ...

  9. 基于EasyHook实现监控explorer资源管理器文件复制、删除、剪切等操作

    一.前言 最近自己在研究一个项目,需要实现对explorer资源管理器文件操作的监控功能,网上找到一些通过C++实现Hook explorer文件操作的方法,由于本人习惯用.NET开发程序,加之C/C ...

  10. wpf进程间通讯

    wpf进程间通讯 在联想智能识别项目中,需要用到进程间通讯,并且是低权限向高权限发送消息.首先声明一下,此项目是wpf的. 首先先简要说一下什么时候会用到进程间通讯,如:在Windows程序中,各个进 ...