前端开发:深入了解Vue数据响应式原理
Vue的响应式原理是基于发布订阅模式实现的。所以先来了解一下发布订阅模式【Publish-Subscribe Pattern】
由发布者Publisher、订阅者列表Subscriber、消息队列/调度中心Event Channel组成的形式。发布者Publisher和订阅者Subscriber之间是互不相识,不直接通信交流的,而是通过消息队列进行。
可以理解为:某博主(发布者)发布了一个短视频在短视频平台(消息队列)上,每天上班搬砖的打工人(订阅者)趁着吃饭的时间,刷刷短视频,发现该博主的视频非常优秀有内容,于是在该短视频平台上订阅关注了该视频博主。然后该博主只要更新了视频,你就能接收到通知。

class eventChannel {
constructor() {
this.msgMap = {}
}
publish(name, param) {
const msg = this.msgMap[name]
if (msg) {
msg.subscribeList.forEach(subscribe => {
subscribe.callback(param)
})
}
}
subscribe(name, callback) {
const msg = this.msgMap[name]
if (msg) {
msg.subscribe.push({callback})
} else {
this.msgMap[name] = {
name,
subscribeList: [{callback}]
}
}
}
}
面试有时候会被问到观察者模式和发布-订阅模式之间有什么不同。简单的说:观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件,没有中介代替传递消息。
观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。

let observerIds = 0
// 被观察者
class Subject {
constructor() {
this.observeres = []
}
addObservere(observer) {
this.observeres.push(observer)
}
removeObservere(obs) {
this.observeres = this.observeres.filter(observer => observer !== obs )
}
notify(data) {
this.observeres.forEach(observer => observer(data))
}
}
// 观察者
class Observer {
constructor() {
this.id = observerIds++
}
update(ob) {
// ...
}
}

Vue2的数据对象是通过Object.defineProperty进行数据劫持, 将其对象属性转化为响应式属性。同时,为每个属性创建一个Dep对象,收集当前属性的依赖关系。这里的Dep就相当于上文发布订阅模式中讲到的调度中心。
当读取数据时会触发getter,修改数据时会触发setter。Watcher对象作用是建立依赖关系,检测数据变化,并在数据变化时执行相应的更新操作。Watcher机制是基于异步更新的,这样能避免频繁更新,提高性能和优化用户体验。
Vue3 是基于proxy的Observer,通过Proxy拦截对象属性变化,通过Reflect对原对象属性进行操作。
Proxy:在目标对象之前假设一层拦截,可以对外界的访问进行改写。可以通过其get捕获器拦截读取对象属性,通过set捕获器拦截对象属性赋值。
Reflect:提供了一组和Objct类似的方法,用于操作对象。Vue3中主要配合Proxy进行操作,以获取/设置代理对象属性值。

Vue3使用Proxy替代Obejct.defineProperty,解决了以下问题:
Proxy可以直接拦截数组索引设置和长度变更。Proxy代理整个对象,无论层级多深都能有效追踪数据变化。