响应式原理的实现

64 篇文章 6 订阅
61 篇文章 1 订阅

什么是响应式

自动响应数据变量的代码机制,我们就称之为是响应式的

通过数据劫持和发布者-订阅者模式来实现。一旦发现属性发生改变就通知订阅者,触发相应的回调,来改变视图。

一、Vue3响应式原理的实现

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

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

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

const obj = {
  name: 'fuyou',
  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: 'fuyou',
  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: 'fuyou',
  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: 'fuyou',
  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: 'fuyou',
  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: 'fuyou',
  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: 'fuyou',
//   age: 18
// }
// const objProxy = reactive(obj)

// 第二种方式:简写
const objProxy = reactive({
  name: 'fuyou',
  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: 'fuyou',
//   age: 18
// }
// const objProxy = reactive(obj)

// 第二种方式:简写
const objProxy = reactive({
  name: 'fuyou',
  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'
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值