Vue.nextTick浅析
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=>{
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(()=>{
let li = this.$refs.ul.querySelectorAll('li')
li.forEach(item=>{
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 < copies.length; i++) {
copies[i]()
}
}
接着定义函数marcoTimerFunc
、microTimerFunc
。
先判断是否支持setImmediate
,如果支持,使用setImmediate
执行回调队列;如果不支持,判断是否支持MessageChannel
,支持时,在port1
监听message
,将flushCallbacks
作为回调;如果仍不支持MessageChannel
,使用setTimeout(flushCallbacks, 0)
执行回调队列。不管使用哪种方式,macroTimerFunc
最终目的都是在一个宏任务里执行回调队列。
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
} else if (typeof MessageChannel !== 'undefined' && (
isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
/* istanbul ignore next */
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
然后判断是否支持Promise
,支持时,新建一个状态为resolved
的Promise
对象,并在then
回调里执行回调队列,如此,便在一个微任务中执行回调,在IOS的UIWebViews组件中,尽管能创建一个微任务,但这个队列并不会执行,除非浏览器需要执行其他任务;所以使用setTimeout
添加一个不执行任何操作的回调,使得微任务队列被执行。如果不支持Promise
,使用降级方案,将microTimerFunc
指向macroTimerFunc
。
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
microTimerFunc = () => {
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(() => {
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 && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
而全局方法Vue.nextTick
在/src/core/global-api/index.js
中声明,是对函数nextTick
的引用,所以使用时可以显示指定执行上下文。
Vue.nextTick = nextTick
小结
本文关于nextTick
的使用场景和源码做了简单的介绍,如果想深入了解这部分的知识,可以去了解一下微任务mircotask
和宏任务marcotask
。
来源:https://segmentfault.com/a/1190000016495892
Vue.nextTick浅析的更多相关文章
- Vue中的nextTick()浅析
引言 在开发过程中,我们经常遇到这样的问题:我明明已经更新了数据,为什么当我获取某个节点的数据时,却还是更新前的数据? 一,浅析 为什么会这样呢?带着这个疑问先往下看. 先看一个小的例子: <d ...
- vue2.0 正确理解Vue.nextTick()的用途
什么是Vue.nextTick() 官方文档解释如下: 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 获取更新后的DOM,言外之意就是DOM更新 ...
- Vue nextTick 机制
背景 我们先来看一段Vue的执行代码: export default { data () { return { msg: 0 } }, mounted () { this.msg = 1 this.m ...
- vue nextTick使用
Vue nextTick使用 vue生命周期 原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue. ...
- Vue.$nextTick
`Vue.nextTick(callback)`,当数据发生变化,更新后执行回调. `Vue.$nextTick(callback)`,当dom发生变化,更新后执行的回调
- Vue 之 Vue.nextTick()
异步更新队列 可能你还没有注意到,Vue 异步执行 DOM 更新.只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变.如果同一个 watcher 被多次触发,只会一次 ...
- Vue.nextTick和Vue.$nextTick
`Vue.nextTick(callback)`,当数据发生变化,更新后执行回调. `Vue.$nextTick(callback)`,当dom发生变化,更新后执行的回调. 参考原文:http://w ...
- 我理解的关于Vue.nextTick()的正确使用
什么是Vue.nextTick() 官方文档解释如下: 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 我理解的官方文档的这句话的侧重点在最后那半 ...
- 全面解析Vue.nextTick实现原理
vue中有一个较为特殊的API,nextTick.根据官方文档的解释,它可以在DOM更新完毕之后执行一个回调,用法如下: // 修改数据 vm.msg = 'Hello' // DOM 还没有更新 V ...
随机推荐
- HTTP与HTTPS区别(详细)
转:http://blog.sina.com.cn/s/blog_6eb3177a0102x66r.html 1.减少http请求(合并文件.合并图片)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 ...
- codevs 1057 津津的储蓄计划 2004年NOIP全国联赛提高组 x
时间限制: 1 s 空间限制: 128000 KB 题目描述 Description 津津的零花钱一直都是自己管理.每个月的月初妈妈给津津300元钱,津津会预算这个月的花销,并且总能做到实际花 ...
- [CF780C]Andryusha and Colored Balloons 题解
前言 完了,完了,咕值要没了,赶紧写题解QAQ. 题意简述 给相邻的三个节点颜色不能相同的树染色所需的最小颜色数. 题解 这道题目很显然可以用深搜. 考虑题目的限制,如果当前搜索到的点为u, 显然u的 ...
- sh_04_第1个函数改造
sh_04_第1个函数改造 name = "小明" # say_hello() # Python 解释器知道下方定义了一个函数 def say_hello(): "&qu ...
- 大哥带我们的mysql注入 基于时间的盲注
?id= and ,,sleep()) ?id= and ,,sleep()) if语句/if()函数 在基于时间型SQL盲注中,我们经常使用条件语句来判断我们的操作是否正确: ?id= and = ...
- DjangoRestFrameWork 版本控制
DRF的版本控制 为什么需要版本控制 API 版本控制允许我们在不同的客户端之间更改行为(同一个接口的不同版本会返回不同的数据). DRF提供了许多不同的版本控制方案. 可能会有一些客户端因为某些原因 ...
- Android Studio 安装 Flutter
1 下载sdk https://flutter.dev/docs/get-started/install/windows 2 解压到自定义文件夹,并配置bin路径到环境变量path中 path添加 ...
- C:\WINDOWS\system32\drivers\etc\hosts文件的作用
在网络上访问网站,要首先通过DNS服务器把网络域名(www.XXXX.com)解析成XXX.XXX.XXX.XXX的IP地址后,我们的计算机才能访问.要是对于每个域名请求我们都要等待域名服务器解析后返 ...
- WPF WebBrowser 加载 html ,出现安全警告, 运行 脚本和 activeX 控件,
对于你的问题,只需要在你的HTML首行添加如下代码即可隐藏安全提示条: <!-- saved from url=(0014)about:internet --> 还有一个可选方案是使用Wi ...