变化侦测

139 阅读2分钟

什么是变化侦测?

vue.js会自动通过状态生成DOM,并将其输出到页面上显示出来,这个过程叫渲染。vue的渲染时声明式的,通过模板来描述状态与DOM之间的映射关系。变化侦测用来解决状态中发生什么变化的问题。 变化侦测的作用是侦测数据的变化。当数据变化时,会通知视图进行相应的更新。

Object的变化侦测

  • 通过Object.defineProperty和Proxy追踪变化
  • 观察数据的目的是当数据的属性发生变化时,可以通知那些曾经使用了该数据的地方
  • 把依赖(Wather:属性变化后通知的目标)收集到dep中。

image.png

Watcher类

原理

把自己设置到全局唯一指定位置。读取数据就会触发数据的getter,在getter中就会从全局唯一的位置读取当前正在读取数据的watcher,并把watcher收集到Dep中去。

/**
 * 数据变化通知Watcher,它再通知其他地方
 */
// 解析简单的路径
const bailRE = /[^\w.$]/
function parsePath(path) {
  if (bailRE.test(path)) {
    return 0
  }
  const segments = path.split('.')
  return (obj) => {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}
class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm
    // 执行getter读取内容
    this.getter = parsePath(expOrFn)
    this.cb = cb
    this.value = this.get()
  }

  get() {
    window.target = this
    const value = this.getter.call(this.vm, this.vm)
    window.target = undefined
    return value
  }

  update() {
    const oldVal = this.value
    this.value = this.get()
    this.cb.call(this.vm, this.value, oldVal)
  }
}

Dep类

/**
 * 管理依赖:收集依赖、删除依赖、向依赖发送通知
 */

// 移除元素函数
function remove(arr, item) {
  if (arr.length) {
    const index = arr.indexOf(item)
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

class Dep {
  constructor() {
    this.subs = []
  }

  // 收集依赖
  addSub(sub) {
    console.log('正在收集依赖...')
    this.subs.push(sub)
  }

  // 删除依赖
  removeSub(sub) {
    console.log('正在删除依赖...')
    remove(this.subs, sub)
  }

  depend() {
    if (window.target) {
      this.addSub(window.target)
    }
  }

  // 向依赖发送通知
  notify() {
    console.log('正在向依赖发送通知...')
    const subs = this.subs.slice()
    for (let i = 0, l = this.subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

Observer类

/**
 * Observer类会附加到每一个被侦测的object上
 * 侦听所有的属性,将数据内的所有属性都转换成getter/setter的形式,然后去追踪变化
 */
class Observer {
  constructor(value) {
    this.value = value
    if (!Array.isArray(value)) {
      this.walk(value)
    }
  }

  // 数据类型为Object时,将每一个属性都转换成getter/setter的形式
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }
}

defineReactive函数

function defineReactive(data, key, val) {
  // 递归子属性
  if (typeof val === 'object') {
    const observer = new Observer(val)
  }
  const dep = new Dep()
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get() {
      dep.depend()
      return val
    },
    set(newVal) {
      if (val === newVal) {
        return
      }
      val = newVal
      dep.notify()
    },
  })
}

参考文献

《深入浅出Vue.js》刘博文