详解从Vue.js源码看异步更新DOM策略及nextTick

写在前面

因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。

文章的原地址:https://github.com/answershuto/learnVue。

在学习过程中,为Vue加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习Vue源码的小伙伴有所帮助。

可能会有理解存在偏差的地方,欢迎提issue指出,共同学习,共同进步。

操作DOM

在使用vue.js的时候,有时候因为一些特定的业务场景,不得不去操作DOM,比如这样:

<template>
 <div>
 <div ref="test">{{test}}</div>
 <button @click="handleClick">tet</button>
 </div>
</template>
export default {
 data () {
  return {
   test: 'begin'
  };
 },
 methods () {
  handleClick () {
   this.test = 'end';
   console.log(this.$refs.test.innerText);//打印“begin”
  }
 }
}

打印的结果是begin,为什么我们明明已经将test设置成了“end”,获取真实DOM节点的innerText却没有得到我们预期中的“end”,而是得到之前的值“begin”呢?

Watcher队列

带着疑问,我们找到了Vue.js源码的Watch实现。当某个响应式数据发生变化的时候,它的setter函数会通知闭包中的Dep,Dep则会调用它管理的所有Watch对象。触发Watch对象的update实现。我们来看一下update的实现。

update () {
 /* istanbul ignore else */
 if (this.lazy) {
  this.dirty = true
 } else if (this.sync) {
  /*同步则执行run直接渲染视图*/
  this.run()
 } else {
  /*异步推送到观察者队列中,下一个tick时调用。*/
  queueWatcher(this)
 }
}

我们发现Vue.js默认是使用异步执行DOM更新。

当异步执行update的时候,会调用queueWatcher函数。

 /*将一个观察者对象push进观察者队列,在队列中已经存在相同的id则该观察者对象将被跳过,除非它是在队列被刷新时推送*/
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)
 }
 }
}


      

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/5148.html