vue异步更新队列

  1. Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

  2. 宏任务:script,setTimeout,setInterval,postMessage,messageChannel,setImmediate

  3. 微任务:promise.then,Object.observe,mutationObserver,process.nextTick

  4. 例如,当你设置 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
    })
    
  5. 在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:

    Vue.component('example', {
      template: '{{ message }}',
      data: function () {
        return {
          message: '未更新'
        }
      },
      methods: {
        updateMessage: function () {
          this.message = '已更新'
          console.log(this.$el.textContent) // => '未更新'
          this.$nextTick(function () {
            console.log(this.$el.textContent) // => '已更新'
          })
        }
      }
    })
    
  6. 因为 $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2017 async/await 语法完成相同的事情:

    methods: {
      updateMessage: async function () {
        this.message = '已更新'
        console.log(this.$el.textContent) // => '未更新'
        await this.$nextTick()
        console.log(this.$el.textContent) // => '已更新'
      }
    }
    
  7. 异步:只要监听到数据变化了,vue就开启一个队列,并缓存在同一个时间循环中发生的所有数据变更。

  8. 批量:如果同一个watcher被多次出发,只会被推入到队列中一次。去重对于避免不必要的计算和dom操作非常重要,在下一个时间循环tick中,vue刷新队列执行实际工作

  9. 异步策略: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 = true
        Promise.resolve().then(flushCallbacks)
      }
    }
    

    flushCallbacks

    function flushCallbacks() {
      pending = false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
    

    queueWatcher

    function queueWatcher(watcher) {
      const id = watcher.id
      if (has[id] != null) { // 下次有相同id,就不再进入
        return
      }
      if (watcher === Dep.target && watcher.noRecurse) {
        return
      }
      has[id] = true
      if (!flushing) {
        queue.push(watcher)
      } else {
        let i = queue.length - 1
        while (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 = true
      let watcher, id
      queue.sort(sortCompareFn)
      for (index = 0; index < queue.length; index++) {
        watcher = queue[index]
        if (watcher.before) {
          watcher.before()
        }
        id = watcher.id
        has[id] = null
        watcher.run()
      }
      const activatedQueue = activatedChildren.slice()
      const updatedQueue = queue.slice()
      resetSchedulerState()
      callActivatedHooks(activatedQueue)
      callUpdatedHooks(updatedQueue)
      cleanupDeps()
    }
    

你可能感兴趣的:(vue)