vue 源码分析(Watcher, Dep, computed)

 把这里的代码复制到本地执行,看懂这100行代码,你就一定能看懂 vue

    const data = {
      flag: false,
      firstName: 'Zheng',
      lastName: 'Yimeng'
    }

    const computed = {
      name() {
        if (!data.flag) {
          return '你拿不到'
        }
        return data.firstName + ' ' + data.lastName
      }
    }

    function observe(obj) {
      const keys = Object.keys(obj)
      /** 需要注意这里,我并没有对传入的 obj 本身做响应式处理,是为了简化代码 (vue 源代码对传入的对象也做了处理) */
      keys.forEach(key => {
        observer(obj, key)
      })
    }

    function observer(obj, key) {
      let value = obj[key] //?1 为什么需要这个东西
      let dep = new Dep() // 每个属性都有一个自身的 dep。
      Object.defineProperty(obj, key, {
        get() {
          if (Dep.target) {
            console.log(`收集依赖 ${key}`)
            Dep.target.addDep(dep)
          }
          return value //?1 因为在这里写 obj[key] 的时候相当于重新访问这个 key,会再次触发 get 方法,进入死循环
        },
        set(newVal) {
          if (value === newVal) return
          value = newVal
          if (dep.subs.length) {
            console.log(`${key} 改变了,我要更新它记录的 watcher 了`)
            dep.notify()
          }
        }
      })
    }

    function initComputed(computed) {
      /** 注意我这里搞 computed 重新拆来搞只是为了让大家看明白,vue 源代码和我不一样,但是原理类似 */
      const watchers = {}
      const keys = Object.keys(computed)
      keys.forEach(key => {
        watchers[key] = new Watcher(computed[key])
      })
      return watchers
    }

    function Dep () {
      this.subs = []
    }

    Dep.prototype.notify = function() {
      this.subs.forEach(watcher => watcher.update())
    }

    function Watcher(func) {
      this.get = func
      this.deps = []
      Dep.target = this
      this.value = this.get() // 函数体是 { return data.firstName + ' ' + data.lastName },所以会调用 data.firstName 和 data.lastName 的 get 方法
      Dep.target = null
    }

    Watcher.prototype.addDep = function(dep) {
      // 这里为什么需要记录 dep? 因为:假设现在 flag 为 true,那么我们的 computed.name 收集依赖的时候收集到了 data.flag, data.firstName, data.lastName,这三个属性每个都有一个 dep,修改其中一个都会重新调用 dep.notify() 从而更新 name 值, 但是当我们修改其中的一个属性的时候,这个收集的依赖其实就已经改变了,比如修改了 flag = false,再修改 data.firstName 和 data.lastName 的时候,就不应该再重新求 computed.name 值了,因为走不到那一步,那么 data.firstName 和 data.lastName 的 dep 就应该为空,这样在修改 data.firstName 和 data.lastName 的时候因为他们的 dep 为空就不会重新求 computed.name 值了
      this.deps.push(dep)
      dep.subs.push(this) // 到这里的时候,还记得吗?自身属性的 watcher 就被记录下来了
    }

    Watcher.prototype.update = function() {
      this.deps.forEach(dep => dep.subs = []) // 在这里把该 计算属性 对应的每个 依赖属性 的 dep 都清空
      Dep.target = this
      let value = this.get() // 这里调用 get 后重新收集依赖
      Dep.target = null
      if (this.value !== value) {
        this.value = value
        // value改变,渲染页面
      }
    }

    function MyVue({data, computed}) {
      observe(data)
      this.watchers = initComputed(computed)
      // 这里把属性简单地绑给实例本身
      Object.keys(this.watchers).forEach(key => { // 这里 computed 我假设不让手动修改它,所以不作处理
        Object.defineProperty(this, key, {
          get() {
            return this.watchers[key].value
          }
        })
      })
      Object.keys(data).forEach(key => {
        Object.defineProperty(this, key, {
          get() {
            return data[key]
          },
          set(newVal) {
            data[key] = newVal
          }
        })
      })
    }

    window.vm = new MyVue({data, computed})

    // 在控制台执行
    // vm.name
    // vm.flag = true // 或者 data.flag = true
    // vm.firstName = 'Shuai'
    // vm.flag = false
    // vm.firstName = 'Zheng'
    // 看看打印的东西,是否和预期的一致

猜你喜欢

转载自blog.csdn.net/weixin_42335036/article/details/109286637
DEP