由于Vue是异步驱动视图更新数据的,即当咱们在事件中修改数据时,视图并不会即时的更新,而是等在同一事件循环的全部数据变化完成后,再进行视图更新。相似于Event Loop事件循环机制。javascript
首先咱们看下官网给出的介绍:html
❝❞
{Function} [callback]
{Object} [context]
vue
// 修改数据
vm.msg = 'Hello' // 当咱们在这里调用DOM的数据时,它其实尚未更新 Vue.nextTick(function () { // DOM 更新了 }) // 2.1.0新增 Promise用法 Vue.nextTick() .then(function () { // 此时DOM已经更新 }) 复制代码
❝2.1.0 起新增:若是没有提供回调且在支持 Promise 的环境中,则返回一个 Promise。请注意 Vue 不自带 Promise 的 polyfill,因此若是你的目标浏览器不原生支持 Promise (IE:大家都看我干吗),你得本身提供 polyfill。java
❞
首先,Vue实现响应式并非在数据改变后就当即更新DOM,而是在一次事件循环的全部数据变化后再异步执行DOM更新.ios
有关异步以及事件循环,能够看下我以前写过的一篇关于文章JS中的异步web
若是不想去详细了解,这边我就简单总结一下事件循环:浏览器
同步代码的执行 => 查找异步队列,进入执行栈,执行Callback1[事件循环1] => 查找异步队列,进入执行栈,执行Callback2[事件循环2] => .....app
❝即每一个异步的Callback都会再独立造成一次事件循环 因此咱们能够退出nextTick的触发时机 一次事件循环中的代码执行完毕 => DOM更新 => 触发nextTick的回调 => 进入下一循环dom
❞
可能只凭一些概念性的讲解仍是没法对nextTick机制有很清晰的了解,仍是上个示例来了解一下吧。异步
<template>
<div class="app"> <div ref="contentDiv">{{content}}</div> <div>在nextTick执行前获取内容:{{content1}}</div> <div>在nextTick执行以后获取内容:{{content2}}</div> <div>在nextTick执行前获取内容:{{content3}}</div> </div> </template> <script> export default { name:'App', data: { content: 'Before NextTick', content1: '', content2: '', content3: '' }, methods: { changeContent () { this.content = 'After NextTick' // 在此处更新content的数据 this.content1 = this.$refs.contentDiv.innerHTML //获取DOM中的数据 this.$nextTick(() => { // 在nextTick的回调中获取DOM中的数据 this.content2 = this.$refs.contentDiv.innerHTML }) this.content3 = this.$refs.contentDiv.innerHTML } }, mount () { this.changeContent() } } </script> 复制代码
当咱们打开页面后咱们能够发现结果为:
After NextTick 在nextTick执行前获取内容:Before NextTick 在nextTick执行以后获取内容:After NextTick 在nextTick执行前获取内容:Before NextTick 复制代码
因此咱们能够知道,虽然content1
和content3
得到内容的语句是写在content
数据改变语句以后的,但他们属于同一个事件循环中,因此content1
和content3
获取的仍是 'Before NextTick' ,而content2
得到内容的语句写在nextTick
的回调中,在DOM更新以后再执行,因此获取的是更新后的 'After NextTick'。
下面是一些nextTick的主要应用场景
当在created()
生命周期中直接执行DOM操做是不可取的,由于此时的DOM并未进行任何的渲染。因此解决办法是将DOM操做写进Vue.nextTick()
的回调函数中。或者是将操做放入mounted()
钩子函数中
在咱们更新数据后,若是还有操做要根据更新数据后的DOM结构进行,那么咱们应当将这部分操做放入**Vue.nextTick()**回调函数中
这部分的详细缘由在Vue的官方文档中解释的很是清晰:
❝可能你尚未注意到,Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的全部数据改变。若是同一个 watcher 被屡次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免没必要要的计算和 DOM 操做上很是重要。而后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工做。Vue 在内部尝试对异步队列使用原生的
❞Promise.then
和MessageChannel
,若是执行环境不支持,会采用setTimeout(fn, 0)
代替。 例如,当你设置vm.someData = 'new value'
,该组件不会当即从新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数状况咱们不须要关心这个过程,可是若是你想在 DOM 状态更新后作点什么,这就可能会有些棘手。虽然 Vue.js 一般鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,可是有时咱们确实要这么作。为了在数据变化以后等待 Vue 完成更新 DOM ,能够在数据变化以后当即使用Vue.nextTick(callback)
。这样回调函数在 DOM 更新完成后就会调用。
export const nextTick = (function () {
// 存放全部的回调函数 const callbacks = [] // 是否正在执行回调函数的标志 let pending = false // 触发执行回调函数 let timerFunc // 处理回调函数 function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { // 执行回调函数 copies[i]() } } // nextTick行为利用了微任务队列 // 它能够经过原生Promise或者MutationObserver实现 // MutationObserver已经有了普遍的浏览器支持,然而他仍然在UIWebView在ios系统9.3.3以上的 // 系统有严重的Bug,问题发生在咱们触摸事件的触发时。 // 它会在咱们触发一段时间后彻底中止,因此原生Promise是有效能够利用的,咱们会使用它: /* istanbul ignore if */ if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) // 在有问题的 UIWebViews 中,Promise.then 方法不会彻底的中止,但它可能会在一个 // 奇怪的状态卡住当咱们把回调函数推入一个微任务队列可是这个队列并非在冲洗中,知道 // 浏览器须要作一些其余的任务时,例如:执行一个定时函数。所以咱们能够"强制"微任务队 // 列被冲洗经过加入一个空的定时函数 if (isIOS) setTimeout(noop) } } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // 使用MutationObserver当Promise不可用时, // 例如 PhantomJS, iOS7, Android 4.4 var counter = 1 var observer = new MutationObserver(nextTickHandler) var textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { // 当MutationObserver 和 Promise都不可使用时 // 咱们使用setTimeOut来实现 /* istanbul ignore next */ timerFunc = () => { setTimeout(nextTickHandler, 0) } } return function queueNextTick (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 timerFunc() } if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve = resolve }) } } })() 复制代码
咱们经过源码能够知道,timeFunc这个函数起延迟执行的做用,它有三种实现方式
其中Promise
和 setTimeout
咱们都不陌生,下面重点介绍一下MutationObserver
MutationObserver
是HTML5中的新API,是个用来监视DOM变更的接口。他能监听一个DOM对象上发生的子节点删除、属性修改、文本内容修改等等。 调用过程很简单,可是有点不太寻常:你须要先给他绑回调:
let mo = new MutationObserver(callback)
复制代码
经过给MutationObserver
的构造函数传入一个回调,能获得一个MutationObserver
实例,这个回调就会在MutationObserver
实例监听到变更时触发。
这个时候你只是给MutationObserver
实例绑定好了回调,他具体监听哪一个DOM、监听节点删除仍是监听属性修改,尚未设置。而调用他的observer
方法就能够完成这一步:
var domTarget = 你想要监听的dom节点
mo.observe(domTarget, { characterData: true //说明监听文本内容的修改。 }) 复制代码
在nextTick
中 MutationObserver
的做用就以下图所示。在监听到DOM更新后,调用回调函数。
本文使用 mdnice 排版