Vue source code analysis (Watcher, Dep, computed)

 Copy the code here and execute it locally. If you understand these 100 lines of code, you will be able to understand 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'
    // 看看打印的东西,是否和预期的一致

 

Guess you like

Origin blog.csdn.net/weixin_42335036/article/details/109286637