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]()
}
}

接着定义函数marcoTimerFuncmicroTimerFunc

先判断是否支持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,支持时,新建一个状态为resolvedPromise对象,并在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设置为真。判断回调执行是宏任务还是微任务,分别通过marcoTimerFuncmicroTimerFunc来触发回调队列。最后返回一个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. HTTP与HTTPS区别(详细)

    转:http://blog.sina.com.cn/s/blog_6eb3177a0102x66r.html 1.减少http请求(合并文件.合并图片)2.优化图片文件,减小其尺寸,特别是缩略图,一定 ...

  2. Java对数组和列表的排序1.8新特性

    Java对数组列表的排序 数组 Integer[] a = new Integer[] { 1, 2, 3, 4, 5, 6, 9, 8, 7, 4, 5, 5, 6, 6 }; Arrays.sor ...

  3. codevs 1057 津津的储蓄计划 2004年NOIP全国联赛提高组 x

     时间限制: 1 s  空间限制: 128000 KB   题目描述 Description 津津的零花钱一直都是自己管理.每个月的月初妈妈给津津300元钱,津津会预算这个月的花销,并且总能做到实际花 ...

  4. [CF780C]Andryusha and Colored Balloons 题解

    前言 完了,完了,咕值要没了,赶紧写题解QAQ. 题意简述 给相邻的三个节点颜色不能相同的树染色所需的最小颜色数. 题解 这道题目很显然可以用深搜. 考虑题目的限制,如果当前搜索到的点为u, 显然u的 ...

  5. sh_04_第1个函数改造

    sh_04_第1个函数改造 name = "小明" # say_hello() # Python 解释器知道下方定义了一个函数 def say_hello(): "&qu ...

  6. 大哥带我们的mysql注入 基于时间的盲注

    ?id= and ,,sleep()) ?id= and ,,sleep()) if语句/if()函数 在基于时间型SQL盲注中,我们经常使用条件语句来判断我们的操作是否正确: ?id= and = ...

  7. DjangoRestFrameWork 版本控制

    DRF的版本控制 为什么需要版本控制 API 版本控制允许我们在不同的客户端之间更改行为(同一个接口的不同版本会返回不同的数据). DRF提供了许多不同的版本控制方案. 可能会有一些客户端因为某些原因 ...

  8. Android Studio 安装 Flutter

    1 下载sdk https://flutter.dev/docs/get-started/install/windows   2 解压到自定义文件夹,并配置bin路径到环境变量path中 path添加 ...

  9. C:\WINDOWS\system32\drivers\etc\hosts文件的作用

    在网络上访问网站,要首先通过DNS服务器把网络域名(www.XXXX.com)解析成XXX.XXX.XXX.XXX的IP地址后,我们的计算机才能访问.要是对于每个域名请求我们都要等待域名服务器解析后返 ...

  10. WPF WebBrowser 加载 html ,出现安全警告, 运行 脚本和 activeX 控件,

    对于你的问题,只需要在你的HTML首行添加如下代码即可隐藏安全提示条: <!-- saved from url=(0014)about:internet --> 还有一个可选方案是使用Wi ...