以故事的形式讲解 Vue 的 Watcher、Dep、Observer

174 阅读2分钟

代码

下面故事以这个代码为背景。

<template>
  <div>
    {{ magazineMan }}
    <button @click="newEditionMan">男人帮出新品</button>
    <button @click="newEditionWoman">女人帮出新品</button>
  </div>
</template>

<script>

export default {
  data() {
    return {
      magazineMan: '男人帮版本1',
      magazineWoman: '女人帮版本1',
    };
  },
  watch: {
    magazineMan(newValue, oldValue) {
      console.log(newValue);
      console.log(oldValue);
    },
  },
  computed: {
    manAddWoman({ magazineMan, magazineWoman }) {
      return magazineMan + magazineWoman;
    },
  },
  methods: {
    newEditionMan() {
      this.magazineMan = '男人帮版本2';
    },
    newEditionWoman() {
      this.magazineWoman = '女人帮版本2';
    },
  },
};
</script>


故事

公司: data
商品:magazineMan(男人帮)、magazineWoman(女人帮)
客户: watch、manAddWoman、template

watch、manAddWoman、template 他们需要一个特定的身份,所以每个建立一个身份 Watcher,就代表着 data 公司的客户 id号。下面代码是简化过的都是初始化 Watcher 知道 Watcher是什么,其实也没必要理解下面代码

// 2.6.14 (/src/core/instance/init.js)
function initComputed (vm: Component, computed: Object) { 
     watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            // 配置项,computed 默认是懒执行
            computedWatcherOptions
          )
}
// 2.6.14 (/src/core/instance/init.js)
function initWatch (vm: Component, watch: Object) {
   createWatcher(vm, key, handler[i])
}

function createWatcher () {
  return vm.$watch(expOrFn, handler, options)
}

Vue.prototype.$watch = function (): Function {
  const watcher = new Watcher(vm, expOrFn, cb, options)
}
// 每个一个组件都是一个 Watcher
// /src/core/instance/lifecycle.js
export function mountComponent (){
    new Watcher(vm, updateComponent, noop, {
        before () {
          if (vm._isMounted && !vm._isDestroyed) {
            callHook(vm, 'beforeUpdate')
          }
        }
      }, true /* isRenderWatcher */)
}

// /src/platforms/web/runtime/index.js
Vue.prototype.$mount = function (): Component {
  return mountComponent(this, el, hydrating)
}

公司 data 拥有许多杂志,为了方便杂志的管理,所以每个杂志都有两个功能(get、set),get是来收集有哪些顾客订阅来此杂志,将顾客放到 dep 的盒子里,set用来通知订阅的顾客过来拿最新杂志。所以 magazineMan(男人帮) 和 magazineWoman(女人帮) 都有 get 与 set 功能。 下面代码是简化过的(2.6.14 (/src/core/observer/index.js))。

简洁: get -> dep.depend(), set -> dep.notify()

export function defineReactive () {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // get 拦截对 obj[key] 的读取操作
    get: function reactiveGetter () {
      if (Dep.target) {
        // 依赖收集,在 dep 中添加 watcher,也在 watcher 中添加 dep
        dep.depend()
      }
      return value
    },
    // set 拦截对 obj[key] 的设置操作
    set: function reactiveSetter (newVal) {
      // 依赖通知更新
      dep.notify()
    }
  })
}

从上面的背景看到 watch、template 和 manAddWoman 都订阅了 magazineMan(男人帮),manAddWoman 还多订阅了一个 magazineWoman(女人帮)。

当 watch、template 和 manAddWoman 订阅 magazineMan(男人帮) 的时候,magazineMan(男人帮) 就执行 get 函数把这三个客户都放到自己的 dep 盒子里面,manAddWoman 多订阅了 magazineWoman(女人帮),也是一样的操作。

get: function reactiveGetter () {
      if (Dep.target) {
        // 依赖收集,在 dep 中添加 watcher,也在 watcher 中添加 dep
        dep.depend()
      }
      return value
 }

他们想看自己订阅了哪些杂志,在被杂志收集的时候他们也收集来此杂志放到自己的信息下面。

知道他们互相添加就行

export default class Dep {
    depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      },
    addSub (sub: Watcher) {
        this.subs.push(sub)
      }
}

export default class Watcher {
    addDep (dep: Dep) {
         this.newDeps.push(dep)
         dep.addSub(this)
      }
}

当杂志出新版了就拿出自己的 dep 来通知订阅来自己的用户,让他们来拿最新的杂志版本。

 set: function reactiveSetter (newVal) {
      // 依赖通知更新
      dep.notify()
 }

循环通知客户

export default class Dep {
 notify () {
    // 遍历 dep 中存储的 watcher,执行 watcher.update()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

当点击 newEditionMan(男人帮版本升级) 就会去通知 watch、template 和 manAddWoman 来拿最新的男人帮版本。

newEditionWoman(女人帮版本升级) 就只通知 manAddWoman。