-
Vue
在更新DOM
时是异步执行的。只要侦听到数据变化,Vue
将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher
被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM
操作是非常重要的。然后,在下一个的事件循环“tick”
中,Vue
刷新队列并执行实际 (已去重的) 工作。Vue
在内部对异步队列尝试使用原生的Promise.then
、MutationObserver
和setImmediate
,如果执行环境不支持,则会采用setTimeout(fn, 0)
代替。 -
宏任务:
script,setTimeout,setInterval,postMessage,messageChannel,setImmediate
-
微任务:
promise.then,Object.observe,mutationObserver,process.nextTick
。 -
例如,当你设置
vm.someData = 'new value'
,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”
中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的DOM
状态来做点什么,这就可能会有些棘手。虽然Vue.js
通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触DOM
,但是有时我们必须要这么做。为了在数据变化之后等待Vue
完成更新DOM
,可以在数据变化之后立即使用Vue.nextTick(callback)
。这样回调函数将在DOM
更新完成后被调用。例如:<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.$nextTick()
实例方法特别方便,因为它不需要全局Vue
,并且回调函数中的this
将自动绑定到当前的Vue
实例上:Vue.component('example', {template: '<span>{{ message }}</span>',data: function () {return {message: '未更新'}},methods: {updateMessage: function () {this.message = '已更新'console.log(this.$el.textContent) // => '未更新'this.$nextTick(function () {console.log(this.$el.textContent) // => '已更新'})}} })
-
因为
$nextTick()
返回一个Promise
对象,所以你可以使用新的ES2017 async/await
语法完成相同的事情:methods: {updateMessage: async function () {this.message = '已更新'console.log(this.$el.textContent) // => '未更新'await this.$nextTick()console.log(this.$el.textContent) // => '已更新'} }
-
异步:只要监听到数据变化了,vue就开启一个队列,并缓存在同一个时间循环中发生的所有数据变更。
-
批量:如果同一个watcher被多次出发,只会被推入到队列中一次。去重对于避免不必要的计算和dom操作非常重要,在下一个时间循环tick中,vue刷新队列执行实际工作
-
异步策略:
vue
在内部对异步队列禅师使用原生的Promise.then,mutation observe和setImmediate
,如果执行环境不支持,则会采用setTimeout
替代。this.foo = 111 this.foo = 222 this.foo = 333 触发foo的dep.notify() notify(){const subs = this.subs.slice()for(let i =0;L = subs.length;i<L;i++){subs[i].update() // 触发update方法,且执行三遍} } class Watcher{update(){queueWatcher(this)} }
nextTick
function nextTick(cb, ctx) {callbacks.push(() => {cb.call(ctx)})if (!pending) {pending = truePromise.resolve().then(flushCallbacks)} }
flushCallbacks
function flushCallbacks() {pending = falseconst copies = callbacks.slice(0)callbacks.length = 0for (let i = 0; i < copies.length; i++) {copies[i]()} }
queueWatcher
function queueWatcher(watcher) {const id = watcher.idif (has[id] != null) { // 下次有相同id,就不再进入return}if (watcher === Dep.target && watcher.noRecurse) {return}has[id] = trueif (!flushing) {queue.push(watcher)} else {let i = queue.length - 1while (i > index && queue[i].id > watcher.id) {i--}queue.splice(i + 1, 0, watcher)}if (!waiting) {waiting = true// 建立一个微任务,等当前宏任务执行完毕,再执行。nextTick(flushSchedulerQueue)} }
flushSchedulerQueue
function flushSchedulerQueue() {currentFlushTimestamp = getNow()flushing = truelet watcher, idqueue.sort(sortCompareFn)for (index = 0; index < queue.length; index++) {watcher = queue[index]if (watcher.before) {watcher.before()}id = watcher.idhas[id] = nullwatcher.run()}const activatedQueue = activatedChildren.slice()const updatedQueue = queue.slice()resetSchedulerState()callActivatedHooks(activatedQueue)callUpdatedHooks(updatedQueue)cleanupDeps() }