Vue2和Vue3的响应式原理的实现

学习了coderwhy的JavaScript高级语法视频课的笔记

如有错误或者不合适的地方,敬请见谅,欢迎指出和拓展,谢谢各位了

一、Vue3响应式原理的实现

  • 下面的步骤是一步步推导,逐步实现出来的过程。

第一步:响应式函数的封装

// 响应式函数的封装
const reactiveFns = []
function watchFn(fn) {
  reactiveFns.push(fn)
}

const obj = {
  name: 'xwl',
  age: 18
}

// watchFn()封装,区分开和普通函数的区别
watchFn(function () {
  console.log('obj.name的值:' + obj.name)
})
watchFn(function () {
  console.log('obj.name2的值:' + obj.name)
})

obj.name = 'xxx'
reactiveFns.forEach(item => {
  item()
})
复制代码

第二步:依赖收集类的封装

第一步用数组不方便管理,所以用一个class类来管理。

class Depend {
  constructor() {
    this.reactiveFns = []
  }

  addDepend(fn) {
    this.reactiveFns.push(fn)
  }

  notify() {
    this.reactiveFns.forEach(item => {
      item()
    })
  }
}

// 响应式函数的封装
const depend = new Depend()
function watchFn(fn) {
  depend.addDepend(fn)
}

const obj = {
  name: 'xwl',
  age: 18
}

// watchFn()封装,区分开和普通函数的区别
watchFn(function () {
  console.log('obj.name的值:' + obj.name)
})
watchFn(function () {
  console.log('obj.name2的值:' + obj.name)
})

obj.name = 'xxx'
depend.notify()
复制代码

第三步:自动监听对象变化

使用Proxy和Reflect监听对象中的变化。

class Depend {
  constructor() {
    this.reactiveFns = []
  }

  addDepend(fn) {
    this.reactiveFns.push(fn)
  }

  notify() {
    this.reactiveFns.forEach(item => {
      item()
    })
  }
}

// 响应式函数的封装
const depend = new Depend()
function watchFn(fn) {
  depend.addDepend(fn)
}

const obj = {
  name: 'xwl',
  age: 18
}

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    depend.notify()
  }
})

// watchFn()封装,区分开和普通函数的区别
watchFn(function () {
  console.log('obj.name的值:' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2的值:' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age的值:' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20
复制代码

第四步:依赖收集的管理

这就需要用到Map和WeakMap的使用。 第三步问题就是执行 objProxy.age = 20 时,全部响应式函数都执行一遍,没区分name和age对应的响应式函数。

class Depend {
  constructor() {
    this.reactiveFns = []
  }

  addDepend(fn) {
    this.reactiveFns.push(fn)
  }

  notify() {
    this.reactiveFns.forEach(item => {
      item()
    })
  }
}

// 响应式函数的封装
const depend = new Depend()
function watchFn(fn) {
  depend.addDepend(fn)
}

// 获取depend函数的封装
const targetMap = new WeakMap()
function getDepend(target, key) {
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }

  return depend
}

const obj = {
  name: 'xwl',
  age: 18
}

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    const depend = getDepend(target, key)
    depend.notify()
  }
})

// watchFn()封装,区分开和普通函数的区别
watchFn(function () {
  console.log('obj.name的值:' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2的值:' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age的值:' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20
复制代码

第五步:正确的收集依赖

class Depend {
  constructor() {
    this.reactiveFns = []
  }

  addDepend(fn) {
    this.reactiveFns.push(fn)
  }

  notify() {
    this.reactiveFns.forEach(item => {
      item()
    })
  }
}

// 响应式函数的封装
let activeReactiveFn = null //用于给depend.addDepend(activeReactiveFn)传递参数
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 获取depend函数的封装
const targetMap = new WeakMap()
function getDepend(target, key) {
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }

  return depend
}

const obj = {
  name: 'xwl',
  age: 18
}

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    const depend = getDepend(target, key)
    depend.addDepend(activeReactiveFn)
    return Reflect.get(target, key, receiver)
  },
  set(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    const depend = getDepend(target, key)
    depend.notify()
  }
})

// watchFn()封装,区分开和普通函数的区别
watchFn(function () {
  console.log('obj.name的值:' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2的值:' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age的值:' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20
复制代码

第六步:对Depend类重构

这一步的修改有两点:

  • depend.addDepend(activeReactiveFn)改为depend.depend(),定义一个全局变量activeReactiveFn解决传递函数问题;
  • 普通数组 [ ] 改为new Set(),去除重复。
let activeReactiveFn = null

class Depend {
  constructor() {
    //
    this.reactiveFns = new Set()
  }

  // addDepend(fn) {
  //   this.reactiveFns.push(fn)
  // }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(item => {
      item()
    })
  }
}

// 响应式函数的封装
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 获取depend函数的封装
const targetMap = new WeakMap()
function getDepend(target, key) {
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }

  return depend
}

const obj = {
  name: 'xwl',
  age: 18
}

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    const depend = getDepend(target, key)
    // depend.addDepend(activeReactiveFn)
    depend.depend()
    return Reflect.get(target, key, receiver)
  },
  set(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    const depend = getDepend(target, key)
    depend.notify()
  }
})

// watchFn()封装,区分开和普通函数的区别
watchFn(function () {
  // 这里两次使用objProxy.name,就会在数组中添加重复的响应式函数,所以上面用new Set()
  console.log('obj.name的值:' + objProxy.name)
  console.log('obj.name的值111:' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2的值:' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age的值:' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20
复制代码

第七步(最后一步):对象的响应式操作vue3

let activeReactiveFn = null

class Depend {
  constructor() {
    //
    this.reactiveFns = new Set()
  }

  // addDepend(fn) {
  //   this.reactiveFns.push(fn)
  // }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(item => {
      item()
    })
  }
}

// 响应式函数的封装
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 获取depend函数的封装
const targetMap = new WeakMap()
function getDepend(target, key) {
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }

  return depend
}

// 第一种方式
// const obj = {
//   name: 'xwl',
//   age: 18
// }
// const objProxy = reactive(obj)

// 第二种方式:简写
const objProxy = reactive({
  name: 'xwl',
  age: 18
})

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      const depend = getDepend(target, key)
      // depend.addDepend(activeReactiveFn)
      depend.depend()
      return Reflect.get(target, key, receiver)
    },
    set(target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver)
      const depend = getDepend(target, key)
      depend.notify()
    }
  })
}

// watchFn()封装,区分开和普通函数的区别
watchFn(function () {
  // 这里两次使用objProxy.name,就会在数组中添加重复的响应式函数,所以上面用new Set()
  console.log('obj.name的值:' + objProxy.name)
  console.log('obj.name的值111:' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2的值:' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age的值:' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20

// ---------------------------------------------------
const infoProxy = reactive({
  address: 'gd'
})

watchFn(function () {
  console.log('-----info.address的值:' + infoProxy.address)
})

infoProxy.address = 'bj'
复制代码

二、Vue2响应式原理的实现

vue2和vue3实现的逻辑是差不多的,vue2是用Object.defineProperty(),vue3使用Proxy和Reflect。

let activeReactiveFn = null

class Depend {
  constructor() {
    //
    this.reactiveFns = new Set()
  }

  // addDepend(fn) {
  //   this.reactiveFns.push(fn)
  // }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(item => {
      item()
    })
  }
}

// 响应式函数的封装
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 获取depend函数的封装
const targetMap = new WeakMap()
function getDepend(target, key) {
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }

  return depend
}

// 第一种方式
// const obj = {
//   name: 'xwl',
//   age: 18
// }
// const objProxy = reactive(obj)

// 第二种方式:简写
const objProxy = reactive({
  name: 'xwl',
  age: 18
})

function reactive(obj) {
  Object.keys(obj).forEach(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
      get: function () {
        const depend = getDepend(obj, key)
        depend.depend()
        return value
      },
      set: function (newValue) {
        value = newValue
        const depend = getDepend(obj, key)
        depend.notify()
      }
    })
  })
  return obj
}

// watchFn()封装,区分开和普通函数的区别
watchFn(function () {
  // 这里两次使用objProxy.name,就会在数组中添加重复的响应式函数,所以上面用new Set()
  console.log('obj.name的值:' + objProxy.name)
  console.log('obj.name的值111:' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2的值:' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age的值:' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20

// ---------------------------------------------------
const infoProxy = reactive({
  address: 'gd'
})

watchFn(function () {
  console.log('-----info.address的值:' + infoProxy.address)
})

infoProxy.address = 'bj'
复制代码

猜你喜欢

转载自juejin.im/post/7080456357811486751