Vue响应式原理

一、什么是Vue响应式

数据发生变化后,会重新对页面渲染,这就是Vue响应式。

完成这个过程,我们需要:

  • 数据劫持 / 数据代理(侦测数据的变化)
  • 依赖收集(收集视图依赖了哪些数据)
  • 发布订阅模式(数据变化时,自动“通知”需要更新的视图部分,并进行更新)

二、实现Vue响应式

1.数据劫持/数据代理

1.1 方法1.Object.defineProperty

Vue通过设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

Vue 通过 Object.defineProperty来将对象的key转换成 getter/setter的形式来追踪变化,但 getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性。如果是删除属性,我们可以用 vm.$delete实现,那如果是新增属性,该怎么办呢?

(1)可以使用 Vue.set(location,a,1) 方法向嵌套对象添加响应式属性
(2)也可以给这个对象重新赋值,比如 data.location={…data.location,a:1}

1.2 方法2.Proxy

ES6提供了元编程的能力,所以有能力拦截,Vue3.0可能会用ES6中Proxy 作为实现数据代理的主要方式。

Proxy 的代理是针对整个对象的,而不是对象的某个属性,因此不同于 Object.defineProperty 的必须遍历对象每个属性, Proxy 只需要做一层代理就可以监听同级结构下的所有属性变化,当然对于深层结构,递归还是需要进行的。此外 Proxy支持代理数组的变化。但是 Proxy兼容性不太好!

2.依赖收集

依赖收集的核心思想是“事件发布订阅模式”。

2.1Dep订阅者

收集依赖需要为依赖找一个存储依赖的地方,为此我们创建了Dep。它用来收集依赖、删除依赖和向依赖发送消息等。订阅者 Dep 类,主要作用是用来存放 Watcher 观察者对象。我们可以把Watcher理解成一个中介的角色,数据发生变化时通知它,然后它再通知其他地方。
Dep的简单实现:

  • 用 addSub 方法可以在目前的 Dep 对象中增加一个 Watcher 的订阅操作;
  • 用 notify 方法通知目前 Dep 对象的 subs 中的所有 Watcher 对象触发更新操作。
  • 当需要依赖收集的时候调用 addSub,当需要派发更新的时候调用 notify

2.2Watcher观察者

Vue 中定义一个 Watcher 类来表示观察订阅依赖

为啥引入Watcher当属性发生变化后,我们要通知用到数据的地方,而使用这个数据的地方有很多,而且类型还不一样,既有可能是模板,也有可能是用户写的一个watch,这时需要抽象出一个能集中处理这些情况的类。然后,我们在依赖收集阶段只收集这个封装好的类的实例进来,通知也只通知它一个,再由它负责通知其他地方。

依赖收集的目的是将观察者 Watcher 对象存放到当前闭包中的订阅者 Dep 的 subs 中。
在这里插入图片描述
Watcher的简单实现

在执行构造函数的时候将 Dep.target 指向自身,从而使得收集到了对应的 Watcher,在派发更新的时候取出对应的 Watcher ,然后执行 update 函数。

3.代码实现

const obj = {}
Object.keys(obj).forEach(key => {
  let value = obj[key]
  Object.defineProperty(obj, key, {
    get() {
      console.log('试图读取obj的a属性')
    },
    set(newValue) {
      console.log('试图改变obj的a属性')
      //属性变化时进行对Watcher实例进行通知
      dep.notify()
    }
  })
})
//发布者
class Dep{
  constructor(){
    this.subs = []
  }
  addSub(watch){
    this.subs.push(watch)
  }
  //属性变化发送通知函数
  notify(){
  //接受到通知后 调用update函数进行视图的更新
    this.subs.forEach(item => {
      item.update()
    })
  }
}
//订阅者
class Watcher {
  constructor(name){
    this.name =name
  }
  update(){
    console.log(this.name+'发生update');
  }
}
//模拟创建一个发布者
const dep = new Dep()
//模拟创建一个订阅者
const w1 = new Watcher('第一个实例')
//将订阅者添加到发布者中subs的数组里进行管理
dep.addSub(w1)
const w2 = new Watcher('第二个实例')
dep.addSub(w2)
const w3 = new Watcher('第三个实例')
dep.addSub(w3)

三、总结

如何收集依赖,总结起来就一句话,在getter中收集依赖,在setter中触发依赖。先收集依赖,即把用到该数据的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循环触发一遍就行了。

具体来说,当外界通过Watcher读取数据时,便会触发getter从而将Watcher添加到依赖中,哪个Watcher触发了getter,就把哪个Watcher收集到Dep中。当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值