vue2/vue3响应式原理的实现---面试重点

一、什么是响应式原理

先看vue的代码:

当data里面的message发生改变,模板里的message也会进行更新,所以响应式是当某个变量发生改变,某些操作会自动执行

<template>
  <div>
    {{ message }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "hello world",
    };
  },
};
</script>

<style lang="scss" scoped></style>

二、vue3的响应式原理实现

分几个步骤来实现最终的响应式原理

1.响应式函数的封装

思路:

  • 封装一个新的函数watchFn

  • 凡是传入到watchFn的函数,就是需要响应式的

  • 其他默认定义的函数都是不需要响应式的

  • 我们可以声明一个数组来收集这些函数

缺点:

  • 我们在实际开发中需要监听很多对象的响应式;

  • 这些对象需要监听的不只是一个属性,它们很多属性的变化,都会有对应的响应式函数;

  • 我们不可能在全局维护一大堆的数组来保存这些响应函数;

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

// 声明一个数组装需要响应的函数
const reactiveFns = []
// 监听函数 参数是函数 将函数push进reactiveArray
function watchFn(fn) {
  reactiveFns.push(fn)
}

watchFn(function() {
  const newName = obj.name
  console.log("hello");
})

watchFn(function() {
  console.log(obj.name, "demo function-----");
})

obj.name = 'zyk'
reactiveFns.forEach(fn => {
  fn()
})

2.依赖收集类的封装

思路:

设计一个类,这个类用于管理某一个对象的某一个属性的所有响应式函数

class Depend {
  constructor() {
    this.reactiveFns = []
  }
  addDepend(reactiveFn) {
    this.reactiveFns.push(reactiveFn)
  }
  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}
const depend = new Depend()
function watchFn(fn) {
  depend.addDepend(fn)
}

3.自动监听对象属性的变化

思路:使用Proxy为代理对象

// 自动监听属性值的变化
const objProxy = new Proxy(obj, {
  get: function(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set: function(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    depend.notify()
  }
})

4.对象依赖管理的实现

// 封装一个depend函数
const targetMap = new WeakMap()
function getDepends(target, key) {
  // 根据target对象获取map的过程
  let map = targetMap.get(target)
  // 判断map是否有属性 如果没有则创建一个map对象再将map对象和对应的对象存进WeakMap中
  if(!map) {
    map = new Map()
    targetMap.set(target, map)
  }
  // 根据key获取depend对象
  let depend = map.get(key)
  // 判断depend是否有内容 如果没有则创建一个depend类 再将depend和对应的属性存进map中
  if(!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

5.正确的依赖收集

之前收集依赖的地方是在 watchFn 中,但是这种收集依赖的方式我们根本不知道是哪一个key的哪一个depend需要收集依赖,只能针对一个单独的depend对象来添加依赖对象,在我们调用了Proxy的get捕获器时收集依赖,因为如果一个函数中使用了某个对象的key,那么它应该被收集依赖

// 封装一个响应函数
// 声明一个全局变量activeReactiveFn
let activeReactiveFn = null
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 自动监听属性值的变化
const objProxy = new Proxy(obj, {
  get: function(target, key, receiver) {
    // 根据target.key获取对应的depend
    const depend = getDepends(target, key)
    // 给depend对象中添加响应函数
    depend.addDepend(activeReactiveFn)
    return Reflect.get(target, key, receiver)
  },
  set: function(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    const depend = getDepends(target, key) // 获取对应的对象里面的对应的一个depend
    depend.notify()
  }
})

6.总的思路

  • 响应式函数的封装

    • 声明一个全局变量activeReactiveFn

    • 调用一次响应式函数

    • 将全局变量activeReactiveFn 设为空

  • 响应式依赖收集类的封装

    • 封装一个Depend类,收集targetMap(响应的对象) -> map(对应的属性) -> depend(对应的响应式函数)

  • 自动监听对象属性变化

  • 封装一个获取depend对象的函数,对象依赖的管理,根据对象和它的key来获取对应的depend

代码示例:

// 保存当前需要保存的响应式函数
let activeReactiveFn = null

// 响应式依赖的收集
class Depend {
  constructor() {
    // 优化:保证不会上传同样的函数
    this.reactiveFns = new Set()
  }

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

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

// 收集依赖函数
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
  // 根据对象获取map值
  let map = targetMap.get(target)
  // 第一次先创建map
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }
  // 根据key获取depend
  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

// 监听对象的属性变量
function reactive(obj) {
  // 返回一个proxy对象
  return new Proxy(obj, {
    get: function(target, key, receiver) {
      // 获取depend对象
      const depend = getDepend(target, key)
      // 调用depend方法 将响应式函数push进this.reactiveFns
      depend.depend()
      return Reflect.get(target, key, receiver)
    },
    set: function(target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver)
      const depend = getDepend(target, key)
      // 运行依赖的响应函数
      depend.notify()
    }
  })
}

const objProxy = reactive({
  name: 'kk',
  age: 18
})

const infoProxy = reactive({
  name: 'ss',
  address: '广州'
})

watchFn(function objNameFn() {
  console.log(objProxy.name, '--------');
})

watchFn(function infoAddressFn() {
  console.log(infoProxy.address);
})

objProxy.name = 'ss'
infoProxy.address = '北京'

三、vue2的响应式原理实现

vue3使用的proxy对象,vue2使用Object.defindProperty来监听对象属性的变化
只需要修改这一部分

// 创建出来一个函数,针对所有的对象都可以变成响应式对象
function reactive(obj){
  Object.keys(obj).forEach(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
      get: function() {
        const depend = getDepends(obj, key)
        depend.depend()
        return value
      },
      set: function(newValue) {
        const depend = getDepends(obj, key)
        value = newValue
        depend.notify()
      }
    })
  })
  return obj
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值