快速跳转:javascript
引用下vue.js官方教程关于vue中异步更新队列的解释:html
可能你尚未注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的全部数据变动。若是同一个 watcher 被屡次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免没必要要的计算和 DOM 操做是很是重要的。而后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工做。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,若是执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
为何要这么作呢?来看一个简单的例子:vue
<template> <div> <div>{{test}}</div> </div> </template>
export default { data () { return { test: 0 }; }, mounted () { for(let i = 0; i < 1000; i++) { this.test++; } } }
根据mounted里的代码,test一共被触发了1000次,若是不是异步更新,watcher在被触发1000次时,每次都会去更新视图,这是很是很是消耗性能的。所以,vue才会采用异步更新的操做,若是同一个 watcher 被屡次触发,只会被推入到队列中一次。而后在下一次事件循环'tick‘的时候,只需更新一次dom便可。java
咱们来看一下watcher类中的update方法,了解下具体这个异步更新代码是如何实现的。git
update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { /*同步则执行run直接渲染视图*/ // 基本不会用到sync this.run() } else { /*异步推送到观察者队列中,由调度者调用。*/ queueWatcher(this) } } export function queueWatcher (watcher: Watcher) { /*获取watcher的id*/ const id = watcher.id /*检验id是否存在,已经存在则直接跳过,不存在则标记哈希表has,用于下次检验*/ if (has[id] == null) { has[id] = true if (!flushing) { /*若是没有flush掉,直接push到队列中便可*/ queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. let i = queue.length - 1 while (i >= 0 && queue[i].id > watcher.id) { i-- } queue.splice(Math.max(i, index) + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } } }
咱们能够看到,watcher在update时候,调用了一个queueWatcher的方法,将watcher异步推送到观察者队列中;调用queueWatcher的时候,首先查看了watcher的id,保证相同的watcher不被屡次推入;waiting是一个标识,若是没有在waiting, 就异步执行flushSchedulerQueue方法。github
flushSchedulerQueue是下一个tick的时候的回调函数,主要就是去执行watcher中的run方法。看一下源码:express
/*nextTick的回调函数,在下一个tick时flush掉两个队列同时运行watchers*/ function flushSchedulerQueue () { flushing = true let watcher, id // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. /* 给queue排序,这样作能够保证: 1.组件更新的顺序是从父组件到子组件的顺序,由于父组件老是比子组件先建立。 2.一个组件的user watchers比render watcher先运行,由于user watchers每每比render watcher更早建立 3.若是一个组件在父组件watcher运行期间被销毁,它的watcher执行将被跳过。 */ // queue: Array<Watcher> queue.sort((a, b) => a.id - b.id) // do not cache length because more watchers might be pushed // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id /*将has的标记删除*/ has[id] = null /*执行watcher*/ watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } } // keep copies of post queues before resetting state /**/ /*获得队列的拷贝*/ const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() /*重置调度者的状态*/ resetSchedulerState() // call component updated and activated hooks /*使子组件状态都改编成active同时调用activated钩子*/ callActivatedHooks(activatedQueue) /*调用updated钩子*/ callUpdateHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush') } }
这时候咱们再来看一下vue中文教程里的代码:闭包
<div id="example">{{message}}</div>
var vm = new Vue({ el: '#example', data: { message: '123' } }) vm.message = 'new message' // 更改数据 vm.$el.textContent === 'new message' // false Vue.nextTick(function () { vm.$el.textContent === 'new message' // true })
当设置vm.message = 'new message'
时候,该组件并不会当即进行渲染,而是在dep.notify了watcher以后,被异步推到了调度者队列中,等到nextTick的时候进行更新。nextTick则是会去尝试原生的Promise.then和MutationObserver,若是都不支持,最差会采用setTimeout(fn, 0)进行异步更新。因此若是想基于更新后的DOM状态作点什么,能够在数据变化以后当即使用Vue.nextTick(callback)对真实DOM进行操做。dom