深度剖析Vue2、Vue3响应式原理 逐步推敲手写响应式原理全过程_getdepend

方式二:通过new Proxy的方式(vue3采用的方式);

我们这里先以Object.defineProperty的方式来监听:

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

监听后就不需要再手动调用, 当属性发生变化时, 会自动调用notify方法, 实现数据更新

// 修改obj属性
console.log("------------name属性发生改变------------")
obj.name = "kaisa"

console.log("------------age属性发生改变------------")
obj.age = 38

在这里插入图片描述


🍤自动的收集依赖(核心难点)

目前我们是通过watchFn不管三七二十一的, 将函数添加到类中, 当属性改变时重新执行添加到类中的函数

如果向类中添加两个函数, 但是如果其中一个依赖name, 另一个没有依赖name属性, 这样的添加方法是有问题的, 没有依赖name属性的函数, 我们应该不去向类中添加

我们目前是创建了一个Depend对象,用来管理对于name变化需要监听的响应函数

但是实际开发中我们会有不同的对象,另外会有不同的属性需要管理;

我们如何可以使用一种数据结构来管理不同对象的不同依赖关系呢?

在前面ES6新特性中我讲解过WeakMap,并且在讲WeakMap的时候我讲到了后面通过WeakMap如何管理这种响应式的数据依赖(我们会按照如下数据结构对响应式输入依赖进行管理)

dep对象数据结构的管理(最难理解)

  • 每一个对象的每一个属性都会对应一个dep对象
  • 同一个对象的多个属性的dep对象是存放一个map对象中
  • 多个对象分别对应的map对象, 又会被存放到一个objMap的对象中

依赖收集: 当执行get函数, 自动的添加fn函数; 当执行set函数, 自动执行Depend对象的notify方法

在这里插入图片描述

我们可以写一个getDepend函数专门来管理这种依赖关系

这样我们调用getDepend函数, 一定会返回一个depend对象, 并且在get和set方法中, 可以根据obj和key拿到正确的depend对象

// 封装一个函数: 负责通过obj的key获取对应的Depend对象
const objMap = new WeakMap()
function getDepend(obj, key) {
  // 1.根据obj对象, 找到obj对应的map对象
  let map = objMap.get(obj)
  // 当map对象不存在时, 创建一个map对象, 再将obj[map]放到objMap中
  if (!map) {
    map = new Map()
    objMap.set(obj, map)
  }

  // 2.根据key, 找到map对应的depend对象
  let dep = map.get(key)
  // dep没有值时, 创建一个depend对象, 存入对应的map对象中
  if (!dep) {
    dep = new Depend()
    map.set(key, dep)
  }
  return dep
}

接下来我们就需要正确的将依赖收集起来, 我们之前收集依赖的地方是在 watchFn 中

但是之前这种收集依赖的方式我们根本不知道是哪一个key的哪一个depend需要收集依赖, 只能针对一个单独的depend对象来添加你的依赖对象;

那么正确的应该是在哪里收集呢?应该在我们调用了get捕获器时, 因为如果一个函数中使用了某个对象的key,那么它应该被收集依赖, 而又当一个函数中使用了某个对象的key, 那么就会执行该对象的get方法, 我们可以在get捕获器中, 将正确的依赖收集进来;

// 封装函数: 用于收集依赖
// 定义一个变量, 临时保存传入的fn函数, 方便在get中添加到depend对象
let reactiveFn = null
function watchFn(fn) {
  reactiveFn = fn
  // 函数传进来时,会 自动执行一次
  fn()
  reactiveFn = null
}

Object.keys(obj).forEach(key => {
  let value = obj[key]
  Object.defineProperty(obj, key, {
    get: function() {
      // 获取正确的dep, 将函数添加进去
      const dep = getDepend(obj, key)
      dep.addDepend(reactiveFn)
      return value
    }
  }) 
})

当属性改变时, 会执行set方法, 我们在set方法中可以拿到当前dep对象, 并执行当前dep对象的notify方法

Object.keys(obj).forEach(key => {
  let value = obj[key]
  Object.defineProperty(obj, key, {
    set: function(newValue) {
      value = newValue
      // 获取obj.key对应的depend对象
      const dep = getDepend(obj, key)
      dep.notify()
    },
    get: function() {
      // 找到正确的dep, 将函数添加进去
      const dep = getDepend(obj, key)
      dep.addDepend(reactiveFn)
      return value
    }
  }) 
})

按照上诉步骤, 就完成了自动收集依赖的实现, 完整代码如下

// 使用一个类代替数组来进行依赖管理
class Depend {
  constructor() {
    // 定义存放响应式函数的数组
    this.reactiveFns = []
  }

  // 定义实例方法, 用于收集需要响应式的函数
  addDepend(fn) {
    if (fn) {
      this.reactiveFns.push(fn)
    }
  }

  // 定义方法, 用于数据改变时, 执行数组中的响应式的函数
  notify() {
    this.reactiveFns.forEach(fn => fn())
  }
}

const obj = {
  name: "chenyq",
  age: 18
}

// 封装函数: 用于收集依赖
// 定义一个变量, 临时保存传入的fn函数, 方便在get中添加到depend对象
let reactiveFn = null
function watchFn(fn) {
  reactiveFn = fn
  // 函数传进来时,会 自动执行一次
  fn()
  reactiveFn = null
}

// 封装一个函数: 负责通过obj的key获取对应的Depend对象
const objMap = new WeakMap()
function getDepend(obj, key) {
  // 1.根据obj对象, 找到obj对应的map对象
  let map = objMap.get(obj)
  // 当map对象不存在时, 创建一个map对象, 再将obj[map]放到objMap中
  if (!map) {
    map = new Map()
    objMap.set(obj, map)
  }

  // 2.根据key, 找到map对应的depend对象
  let dep = map.get(key)
  // dep没有值时, 创建一个depend对象, 存入对应的map对象中
  if (!dep) {
    dep = new Depend()
    map.set(key, dep)
  }
  return dep
}

Object.keys(obj).forEach(key => {
  let value = obj[key]
  Object.defineProperty(obj, key, {
    set: function(newValue) {
      value = newValue
      // 获取obj.key对应的depend对象
      const dep = getDepend(obj, key)
      dep.notify()
    },
    get: function() {
      // 获取正确的dep, 将函数添加进去
      const dep = getDepend(obj, key)
      dep.addDepend(reactiveFn)
      return value
    }
  }) 
})

// 调用函数, 将函数收集到数类中
watchFn(function foo() {
  console.log("foo:", obj.name)
  console.log("foo:", obj.age)
})

watchFn(function bar() {
  console.log("bar:", obj.age + 10)
})

// 测试: 
// 修改obj属性
console.log("------------name属性发生改变------------")
obj.name = "kaisa"

console.log("------------age属性发生改变------------")
obj.age = 38

🍤对Depend重构

自动收集依赖已经实现, 但是这里有两个小问题:

问题一:如果函数中有用到两次key,比如name,那么这个函数会被收集两次;

问题二:我们并不希望将添加reactiveFn放到get中,以为它是属于Dep的行为;

所以我们需要对Depend类进行重构

解决问题一的方法:不使用数组,而是使用Set;

如下代码, 例如当函数中有使用两次或多次name属性时, name属性也会被多次添加到dep.reactiveFns数组中, 导致函数被多次执行

// 调用函数, 将函数收集到数类中
watchFn(function foo() {
  console.log("foo:", obj.name)
  console.log("foo:", obj.name)
  console.log("foo:", obj.name)
  console.log("foo:", obj.age)
})

watchFn(function bar() {
  console.log("bar:", obj.age + 10)
})

// 测试: 
// 修改obj属性
console.log("------------name属性发生改变------------")
obj.name = "kaisa"

在这里插入图片描述

解决方式: 使用Set结构

class Depend {
  constructor() {
    // 定义存放响应式函数换成Set
    this.reactiveFns = new Set()
  }

  addDepend(fn) {
    if (fn) {
      this.reactiveFns.add(fn)
    }
  }

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

解决问题二的方法:添加一个新的方法,用于收集依赖;

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

  addDepend(fn) {
    if (fn) {
      this.reactiveFns.add(fn)
    }
  }

  // 添加一个新的方法,用于收集依赖
  depend() {
    if (reactiveFn) {
      this.reactiveFns.add(reactiveFn)
    }
  }

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

get: function() {
  const dep = getDepend(obj, key)
	 // 调用depend方法 
  dep.depend()
  return value
}

🍤创建响应式对象

我们目前的响应式是针对于obj一个对象的,我们可以创建出来一个函数,针对所有的对象都可以变成响应式对象

function reactive(obj) {
  Object.keys(obj).forEach(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
      set: function(newValue) {
        value = newValue
        // 获取obj.key对应的depend对象
        const dep = getDepend(obj, key)
        dep.notify()
      },
      get: function() {
        // 获取正确的dep, 将函数添加进去
        const dep = getDepend(obj, key)
        // dep.addDepend(reactiveFn)
        dep.depend()
        return value
      }
    }) 
  })
  return obj
}

当我们想要响应式时, 包裹一层reactive即可

const obj = reactive({
  name: "chenyq",
  age: 18,
  address: "成都市"
})

watchFn(function foo() {
  console.log("foo:", obj.name)
  console.log("foo:", obj.age)
})

const user = reactive({
  userName: 'aaabbbccc',
  passWorld: "abc123"
})

watchFn(function bar() {
  console.log("bar:", user.userName)
  console.log("bar:", user.passWorld)
})

// 测试: 
obj.name = "kaisa"

user.passWorld = "123456"

🍤Vue3响应式原理

我们前面所实现的响应式的代码,其实就是Vue2中的响应式原理

Vue3主要是通过Proxy来监听数据的变化以及收集相关 的依赖的;

Vue2中通过Object.defineProerty 的方式来实现对象属性的监听;

Vue2和Vue3的原理思路是一样的, 我们只需要将reactive函数中的Object.defineProerty重构成Proxy来监听数据, 就是Vue3的响应式原理

function reactive(obj) {
  const objProxy = new Proxy(obj, {
    set: function(target, key, newValue) {
      // 设置新值
      Reflect.set(target, key, newValue)
      const dep = getDepend(target, key)
      dep.notify()
    },
    get: function(target, key) {
      const dep = getDepend(target, key)
      dep.depend()
      return Reflect.get(target, key)
    }
  })
  return objProxy
#### 总结一下



面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。



为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

![](https://img-blog.csdnimg.cn/img_convert/621960a57eb42479e02d6d64c0c81891.png)



![](https://img-blog.csdnimg.cn/img_convert/5230c48fd0fcb265f3401a21603bda2b.png)



**前端面试题汇总**



![](https://img-blog.csdnimg.cn/img_convert/42728594459506983a38ca2b86545fc6.png)

**JavaScript**



![](https://img-blog.csdnimg.cn/img_convert/7796de226b373d068d8f5bef31e668ce.png)



**性能**

![](https://img-blog.csdnimg.cn/img_convert/d7f6750332c78eb27cc606540cdce3b4.png)



**linux**



![](https://img-blog.csdnimg.cn/img_convert/ed368cc25284edda453a4c6cb49916ef.png)



**前端资料汇总**

![](https://img-blog.csdnimg.cn/img_convert/6e0ba223f65e063db5b1b4b6aa26129a.png)

前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值