Vue2和Vue3的响应式实现

187 阅读1分钟

Vue2 响应式

响应式主要只包含三个元素:ObserverDepWatcher

  • Observer:递归地监听对象上的所有属性,在属性值改变的时候,触发相应的watcher
  • Watcher:观察者,当监听的数据值修改时,执行响应的回调函数(Vue里面的更新模板内容)
  • Dep:连接ObserverWatcher的桥梁,每一个Observer对应一个Dep,它内部维护一个数组,保存与该Observer相关的Watcher
class Observer {
  constructor(target, key, val) {
    this.dep = new Dep()
    if (Object.prototype.toString.call(val).slice(8, -1) === 'object') {
      for (let key in val) {
        if (!val.hasOwnProperty(key)) continue
        new Observer(val, key, val[key])
      }
    }
    if (Array.isArray(val)) {
      // 数组方法拦截操作...
      for (let [key, val2] of val) new Observer(val, key, val2)
      return
    }
    this.defineReactive(target, key, val)
  }
  
  defineReactive(target, key, val) {
    const self = this
    Object.defineProperty(target, key, {
      enumerable: true,
      configurable: false,
      get() {
        Dep.target && self.dep.addSub(Dep.target)
        return val
      },
      set(newVal) {
        if (newVal === val) return
        self.dep.notify()
        val = newVal
      },
    })
  }
}

class Dep {
  constructor() {
    this.deps = []
  }
  addSub(dep) {
    if (this.deps.includes(dep)) return
    this.deps.push(dep)
  }
  notify() {
    this.deps.forEach(dep => dep.update())
  }
}

class Watcher {
  constructor(renderFn) {
    this.renderFn = renderFn
  }
  update() {
    Dep.target = this
    this.renderFn()
    Dep.target = null
  }
}

官方原理图如下 image.png

// 测试
let app = document.getElementById('app')
let vm = {
  data: {
    id: 1007,
    goods: { price:1 }
  }
}
let ob = new Observer(vm, 'data', vm.data)
let watcher = new Watcher(() => app.innerHTML = vm.data.goods.price)
watcher.update()

let btn = document.getElementById('btn')
btn.onclick = () => vm.data.goods.price++

对照测试示例如下 image.png

Vue3 响应式

主要由三个部分构成reactivetracktrigger

使用ProxyReflectProxy 机制相对于 Object.defineProperty 在性能上更优,因为 Proxy 可以直接代理整个对象,而不需要遍历属性并逐一添加 gettersetter

function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key)
      return Reflect.get(target, key, receiver)
    },
    set(target, key, val, receiver) {
      if (target[key] === val) return val

      trigger(target, key)
      return Reflect.set(target, key, val, receiver)
    },
  })
}
const targetMap = new WeakMap()

function track(target, key) {
  let depsMap = targetMap.get(target)
    if (!depsMap) {
      depsMap = new Map()
      targetMap.set(target, depsMap)
    }
    let dep = depsMap.get(key)
    if (!dep) {
      dep = new Set()
      depsMap.set(key, dep)
    }
    dep.add(activeEffect) // activeEffect是副作用函数,用于追踪更新视图,由Vue3响应式系统自动处理
}

function trigger(target, key) {
  const dep = targetMap.get(target).get(key)
  dep.forEach(effect => effect())
}