Vue watcher非同步更新的问题

写意风流
  • 178

问题描述

Vue中,如果wather不是同步更新的话,会放到一个队列中,同一事件循环结束后再更新。相关代码再scheduler的queueWatcher方法中。
这里面处理一个场景:
当这个队列正在更新时,又触发了wather。

  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)
    }
  }

不明白为什么要这么处理。直接push到最后不行吗?
现在这样的设计又有什么好处?

追加描述,
vue这段代码也并不是同一个时间循环中,还没执行这个wather,再次触发这个wather就只执行一次。

     queue.splice(i + 1, 0, watcher)

这边是知道这个wather之前的index,并把第二次触发放到这个后面。也还是执行两次update。既然都是执行两次,为什么不直接push到最后

回复
阅读 1.5k
3 个回答

自问自答一下。这个问题其实想说清楚也很简单。之所以要以队列缓存起来,就是为了避免不必要更新。如,

data: {
    a: 1
},
computed: {
    b: function(){
        this.a + 1
    }
}

methods: {
    act: function(){
        this.a = 2;
        // do someting
        this.a = 1
    }
}

在act操作中,我们先改变a,再把它变回来。我们理想状况下是a没变,b也不重新计算。其实想想就知道这是做不到的。因为只要a改变了b的wather就已经在更新队列中了。我们能避免的,只是b的改变已经的其他计算。
这就要求,b的wather执行update的时候要拿到a最新的值来计算。这里就是1。
回到问题中来,如果队列中a的watehr已经更新过,那么就应该把后面的a的wather放到当前更新的wather后面,立即更新。这样可以保证后面的wather用到a是可以拿到最新的值。
同理,如果a的wather还没有更新,那么把新的a的wather放的之前的a的wather的下一位,也是为了保证后面的wather用到a是可以拿到最新的值

想想你在一个函数中不停的修改数据,会发生什么呢

首先,回到update函数,computed会触发依赖的dep.notify()也就是subs[i].update()也就是queueWatcher

  update () {
    /* istanbul ignore else */
    if (this.computed) {
      // A computed property watcher has two modes: lazy and activated.
      // It initializes as lazy by default, and only becomes activated when
      // it is depended on by at least one subscriber, which is typically
      // another computed property or a component's render function.
      if (this.dep.subs.length === 0) {
        // In lazy mode, we don't want to perform computations until necessary,
        // so we simply mark the watcher as dirty. The actual computation is
        // performed just-in-time in this.evaluate() when the computed property
        // is accessed.
        this.dirty = true
      } else {
        // In activated mode, we want to proactively perform the computation
        // but only notify our subscribers when the value has indeed changed.
        this.getAndInvoke(() => {
          this.dep.notify()
        })
      }
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

所以更新的时候还会有computed引发的Watcher进来,所以不能直接push否则顺序就不对了。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
你知道吗?

宣传栏