Watch

如果用户提供了watch方法,会去 initWatch

watch 用的用法挺多,1.值是对象、2.值是数组、3.值是字符串

最终都是通过 createWatcher > vm.$watch 去创建watch

进一步拓展 class Watch

首先注册 $watch 大致如下,在 new Watch 的基础上补了属性,兼容了立即执行

  1. stateMixin(Vue);
  2. export function stateMixin(Vue) {
  3. Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
  4. options.user = true; // 标记为用户watcher
  5. // 核心就是创建个watcher
  6. const watcher = new Watcher(this, exprOrFn, cb, options);
  7. if(options.immediate){
  8. cb.call(vm,watcher.value)
  9. }
  10. }
  11. }

Computed

为啥有缓存?

如果用户提供了 computed 选项,这意味着用到了计算属性:计算属性特点有缓存、响应式、不会主动触发

要理解计算属性,需要先了解watcher,要了解watcher需要先了解响应式原理,懂了计算属性必然理解了后两者,啧啧啧。

思路大纲:

  1. 用户提供了computed
  2. 为每个computed建立watcher new Watcher(vm, userFn, () => {}, { lazy: true }) ,这里不需要回调,设定了 lazy 标识和普通的watcher不一样
  3. watcher中如果lazy=true,默认this.get 时候不调用:不主动触发
  4. 要实现缓存,用到一个flag: dirty ,这意味着符合条件时候dirty=false,就不执行运算,返回某个值;如果不符合条件 dirty=true,重新运算,并且存储起来
  5. 现在wather上需要几个属性:
    1. value存储值
    2. dirty 运算开关
    3. lazy表示是否计算属性
    4. evaluate 执行运算的方法
  6. 隐藏运算到 Object.defineProperty 的get,因为computed可以设定get和set

准备 defineComputed 供initComputed使用,这里通过调用 createComptedGetter 拿到封装后的 desc描述符

交给 Object.defineProperty 使用。这样用户get某个key,得到的是getter方法

  1. function defineComputed(target, key, userDef) {
  2. if (typeof userDef === 'function') {
  3. sharedPropertyDefinition.get = createComputedGetter(key)
  4. } else {
  5. sharedPropertyDefinition.get = createComputedGetter(userDef.get);
  6. sharedPropertyDefinition.set = userDef.set;
  7. }
  8. // 使用defineProperty定义
  9. Object.defineProperty(target, key, sharedPropertyDefinition)
  10. }

createComputedGetter 需要做的是:

  • 返回一个function,后续传递给 desc,也就是高阶函数
  • 准备好和watcher联通

通过阅读发现大致如下:

  1. function createComputedGetter(key) {
  2. return function computedGetter() {
  3. const watcher = this._computedWatchers[key];
  4. if (watcher) {
  5. if (watcher.dirty) { // 如果dirty为true
  6. watcher.evaluate();// 计算出新值,并将dirty 更新为false
  7. }
  8. // 如果依赖的值不发生变化,则返回上次计算的结果
  9. return watcher.value
  10. }
  11. }
  12. }

解读:

  • 高阶函数返回函数,这就是 读key时候的get过程
  • 通过key找到watcher
  • 如果dirty=true,意味着需要执行,那就 evaluate ,就 watcher.get 并且 watcher.dirty=false
  • 否则不执行,返回上次的 value
  • 如果依赖项发生变化 watcher.update 会修改 dirty=true,后续get就会重新计算了

注意 watcher 和 dep的联通:

  • watcher中 watcher.depend()
  • dep中 this.deps[i].depend()

让计算属性中依赖的数据也记录渲染watcher