关于vue.js:100行代码理解vue中的depwatchcomputed机制

1次阅读

共计 4316 个字符,预计需要花费 11 分钟才能阅读完成。

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})

初始化 data

observe(data)
function observe(obj) {const keys = Object.keys(obj)
  /** 须要留神这里,我并没有对传入的 obj 自身做响应式解决,是为了简化代码(vue 源代码对传入的对象也做了解决)*/
  keys.forEach(key => {observer(obj, key)
  })
}

function observer(obj, key) {let value = obj[key] // 存储旧值
  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()}
    }
  })
}

为每个 data 属性生成一个 dep 对象,在属性的 get 办法中,触发 watch.addDep 办法实现 dep 和 watch 的互相收集。在 set 办法中告诉 watch

Dep 对象

subs: 存储 watch
notify: 属性更新了揭示相干 watch

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

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

初始化 computed

this.watchers = initComputed(computed)
function initComputed(computed) {
  /** 留神我这里搞 computed 从新拆来搞只是为了让大家看明确,vue 源代码和我不一样,然而原理相似 */
  const watchers = {}
  const keys = Object.keys(computed)
  keys.forEach(key => {watchers[key] = new Watcher(computed[key])
  })
  return watchers
}

为每个 computed 生成一个 watch。

watch

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 扭转,渲染页面
  }
}

deps:存储相干依赖 dep
1、构造函数中调用了 this.get 办法就会触发相干属性的 get 办法。进而触发 watch.addDep 办法,dep 和 watch 互相收集
2、依赖更新了,watch.update 触发。清空上次的依赖数组;从新计算 computed 值;从新构建依赖数组

正文完
 0