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 ...
随机推荐
- 进程and线程and协程效率对比
1.进程与进程池的效率对比 多进程:p.start()过程中,只是向操作系统发送一个信号,至于什么时候执行,都是操作系统的事情,操作系统接收到信号时,帮该进程申请一块内存空间+拷贝父进程的地址空间 # ...
- Spring Boot教程(四)接收上传的multi-file的文件
构建工程 为例创建一个springmvc工程你需要spring-boot-starter-thymeleaf和 spring-boot-starter-web的起步依赖.为例能够上传文件在服务器,你需 ...
- linux 文本文件显示行数
一般的我们在使用linux编辑器编辑文本文件的时候是不会像其他编程软件一样显示行数 临时方法: 在命令模式下输入:set nu或者:set number都可以为vi设置行号,如果要取消的话,则输入:s ...
- linux服务器在线测速
cd /tmpwget https://raw.github.com/sivel/speedtest-cli/master/speedtest.py或者wget https://raw.githubu ...
- 启动Maven项目时报错Failed to execute goal org.apache.maven.plugins:maven-clean-plugin:2.5:clean (default-clean) on project **-web: Failed to clean project: Failed to delete E:\**\target\tomcat\logs\access_lo
这类错误 出现这种错误,通常是由于您已启动了另一个tomcat 进程或者运行的javaw.exe进程,导致报错. 解决方法: 1. 鼠标点击 X 进行关闭运行失败的 Console页,(如果运行多次, ...
- 第四周实验总结&实验报告
实验二 Java简单类与对象 实验目的 掌握类的定义,熟悉属性.构造函数.方法的作用,掌握用类作为类型声明变量和方法返回值: 理解类和对象的区别,掌握构造函数的使用,熟悉通过对象名引用实例的方法和属性 ...
- AndroidStudio设置SVN忽略文件
方法一: 在SVN中进行设置: 在空白处右键单击,选择TortoiseSVN -> Settings ->General:在General界面找到Global ignore pattern ...
- 2017-03-04 idea破解
https://blog.csdn.net/qq_27686779/article/details/78870816 防止原址被删除,备份下,亲测可用 http://idea.java.sx/ 简单快 ...
- EasyHook Creating a remote file monitor
In this tutorial we will create a remote file monitor using EasyHook. We will cover how to: 使用EasyHo ...
- vim系统剪切板
原文地址 1.vim常用复制粘贴命令 Vim的复制粘贴命令无疑是y (yank),p(paster),加上yy,P PS: vim有个很有意思的约定(我觉得是一种约定),就是某个命令的大小写都是实现某 ...