vue2关于响应式原理源码详解(4)- 派发更新

417 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情

在上三篇文章vue2关于响应式原理源码详解(1)中主要了解了vue的响应式原理,在vue2关于响应式原理源码详解(2)- Dep、Watcher中主要了解了:Dep(观察目标类),Watcher(观察者类),在# vue2关于响应式原理源码详解(3)- 依赖收集中了解了依赖收集的具体流程。本文着重讲解派发更新的具体流程。

派发更新流程

/src/core/observer/dep.ts

此时,模拟当有响应式数据发生改变。会触发set()方法,调用dep.notify()。

image.png

/src/core/observer/watcher.ts

触发update方法,如果不是懒执行和同步执行,会进入queueWatcher()方法。派发更新的重点。

image.png

/src/core/observer/scheduler.ts

接着重点看queueWatcher()方法和flushSchedulerQueue()方法。

function queueWatcher (watcher) { 
   var id = watcher.id; 
   //去重,相同id的watcher不会再次推进队列中
   if (has[id] == null) { 
     has[id] = true; 
     if (!flushing) { 
       queue.push(watcher); //将watcher推入队列中
    } 
    else { 
    var 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); //异步刷新队列
   }
 } 
}

function flushSchedulerQueue () {
    flushing = true;
    var watcher, id;
   
    queue.sort(function (a, b) { return a.id - b.id; }); //对队列做了从⼩到⼤的排序
  
    for (index = 0; index < queue.length; index++) {
      watcher = queue[index];
      id = watcher.id;
      has[id] = null;
      watcher.run();
    }
    const activatedQueue = activatedChildren.slice(); 
    const updatedQueue = queue.slice() ;
    resetSchedulerState();//清状态
  }

queueWatcher方法中,
首先对watcher id 进行了去重判断,因为在一个 renderWatch 中可能会依赖多个观察目标,当我们同时改变多个依赖的值 ,如果 watcher.id 一样的话就不用把两次更新都push到队列中,避免渲染性能消耗。
引入了一个队列的概念,其中flushing 表示 queue 队列的更新状态,flushing为true则表示队列正在更新中。当flushing为false时将watcher推入队列中,最后通过对wating保证对nextTick(flushSchedulerQueue)的调用逻辑只有一次。flushSchedulerQueue 方法是一个微任务。等宏任务结束后调用微任务队列,然后调用flushSchedulerQueue的回调。

flushSchedulerQueue方法中,
通过queue.sort()对队列进行从小到大的排序,是因为
1.父子组件的创建是先父后子,因此watcher的创建和执行顺序也应该是先父后子。而父组件的watcher .id是小于子组件的。
2.用户的自定义watcher的执行和创建应该要优先于渲染watcher
3.如果子组件在父组件watcher执行过程中被销毁,那么需要跳过子组件的watcher,因此父组件要先被执行。
排序后,则对队列进行遍历操作,拿到对应的watcher,执行watcher.run

/src/core/observer/watcher.ts

执行watcher.run,在该方法中会执⾏ this.get() ⽅法求值的时候,会执⾏ getter ⽅法。即会触发组件重新渲染的原因,接着就会重新执⾏ patch 的过程。

image.png

总结

  1. 触发 setter 的时候会调用 dep.notify() 通知所有订阅者进行派发更新
  2. 触发更新时调用update
  3. 触发queueWatcher()方法,这是一个队列,把这些 watcher 先添加到⼀个队列⾥,然后在 nextTick 后才执⾏flushSchedulerQueue
  4. 在 nextTick 后执⾏所有 watcher 的run ,执行了this.get(),最后执⾏它们的回调函数。
  5. 执⾏ this.get() ⽅法求值的时候,会执⾏ getter ⽅法。即会触发组件重新渲染的原因,接着就会重新执⾏ patch 的过程。

大致过程如下: set -> dep.notify() -> subs[i].update() -> watcher.run() || queueWatcher(this) -> watcher.get() || watcher.cb -> watcher.getter() -> vm._update() -> vm.patch()