Vue源码分析之Watcher(二)

1,039 阅读3分钟

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

前言

上篇已经看了Watcher构造函数的实现部分,这篇来具体看下Watcher内部定义的几个函数。

首先看下与Dep相关的两个函数,addDep与update

addDep

在向Dep添加依赖时会调用depend方法,其内部是调用了Watcher的addDep。

depend () {
  if (Dep.target) {
    Dep.target.addDep(this)
  }
}

看一下addDep方法:

addDep (dep: Dep) {
  const id = dep.id
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep)
    if (!this.depIds.has(id)) {
      dep.addSub(this)
    }
  }
}

addDep函数主要做了两件事:

  • 把要订阅的Dep添加到newDeps数组中,并且把其id添加到newDepIds中
  • 把当前Watcher依赖添加到dep中

添加前都需要先判断是否已经被添加过,避免重复添加

update

在依赖更新时会触发Dep的notify方法。其内部调用了Watcher的update方法。

notify () {
    ...
    
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

看一下Watcher类的update函数:

update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this)
  }
}
  • 首先判读lazy值,如果为true,说明是computed Watcher,将dirty设置为true,不计算。

  • 接下来判断sync值,如果为true,说明要同步计算,在当前tick执行watcher的回调。

  • 否则将当前Watcher push到Watcher队列中。queueWatcher函数后边再具体来看。

run

看一下run函数的具体实现:

run () {
  if (this.active) {
    const value = this.get()
    if (
      value !== this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // set new value
      const oldValue = this.value
      this.value = value
      if (this.user) {
        try {
          this.cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "${this.expression}"`)
        }
      } else {
        this.cb.call(this.vm, value, oldValue)
      }
    }
  }
}
  • 首先判断active的值,确保当前Watcher还存活着
  • 接下来调用get拿到Watcher当前最新的value值
  • 接下来判断是否满足执行条件(value!==this.value || isObject(value) || this.deep),如果满足则执行cb回调函数。

depend

depend函数用来将当前Watcher添加到所有它要依赖的dep中。

depend () {
  let i = this.deps.length
  while (i--) {
    this.deps[i].depend()
  }
}

teardown

teardown函数与depend函数相对,用来把当前Watcher从所有它订阅的dep中移除。

teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

evaluate

evaluate () {
  this.value = this.get()
  this.dirty = false
}

evaluate函数主要是用来被computed Watcher调用。

computed Watcher在初始化时并没有求值,是直接赋值为undefined,并且把dirty属性置为true。

当computed Watcher真正需要被求值时,会调用evaluate函数,同时把dirty置为false(说明此时数据已经干净了,是最新的)。

Dep与Watcher

看到这里我们已经可以看出,Dep与Watcher是多对多的关系,Dep的subs数组中保存了收集到的Watcher依赖,而Watcher的deps数组中也保存了自己去订阅的Dep实例。

对象属性在被Object.defineProperty拦截时,每个属性都会有一个自己的Dep实例,里边保存了该属性所有相关的Watcher。

整体来回顾下这个流程:Watcher在取值的时候(get函数)调用pushTarget把全局变量Dep.target置为当前Watcher;然后在调用响应式对象的get拦截时会有一个dep.target判断,如果不为空,则把这个Dep实例添加到该Watcher的newDeps数组中;并且同时判断该dep是否将此Watcher添加到它自己的subs数组中,如果没有添加则将该Watcher添加到Dep实例的subs中。

//  1. pushTarget当前Watcher,src/core/observer/watcher.js
class Watcher{
  get () {
    pushTarget(this)
  }
}

//  2. 将Dep.target置为当前Watcher,src/core/observer/dep.js
function pushTarget(target) {
  targetStack.push(target)
  Dep.target = target
}

//  3. 响应式对象的get触发依赖收集,src/core/observer/index.js
function defineReactive(obj, key, val, customSetter, shallow) {
  Object.defineProperty(obj, key,{
    get: function reactiveGetter() {
      if (Dep.target) {
        dep.depend()
      }
    }
  })
}

//  4. 调用Watcher对象的addDep方法并传入当前的Dep实例,src/core/observer/dep.js
class Dep {
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
}

//  5. 将当前Dep实例dep添加到对应的Watcher实例中的newDeps数组中,同时调用Dep的addSub方法。src/core/observer/watcher.js
class Watcher {
  addDep (dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }
}

// 6. addSub将该Watcher添加到对应的Dep的subs数组中,src/core/observer/dep.js
class Dep {
  addSub (sub) {
    this.subs.push(sub)
  }
}

看一个简单的例子:

<template>
    <div>
        <span>{{firstName}}</span>
        <p>{{userName}}</p>
    </div>
</template>

<script>
export default {
    data() {
        return{
            firstName: 'first',
            lastName: 'last'
        }
    },
    computed: {
        userName() {
            return this.firstName + this.lastName
        }
    },
    watch: {
        'userName'(newVal){
            console.log(newVal)
        }
    }
}
</script>

看一下Watcher列表:

image.png

这里包含三个Watcher:

  • computed Watcher(expression: "function userName() { return this.firstName + '-' + this.lastName; }")
  • user Watcher(expression: "userName")
  • render Watcher(expression: "function (){vm._update(vm._render(), hydrating); }")

看一下Dep(1209)与Dep(1210):

image.png

总体来说,我觉得可以这么理解这个多对多的关系:

image.png