一、什么是观察者模式?
观察者模式,它定义了一种 一对多 的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。在观察者模式中有两个主要角色:Subject(主题或者被观察者)和 Watcher(观察者)。
图片来源 聊聊 observer-util 这个库,让你读懂响应式原理
1.1 定义 Watcher类
interface Observer {
notify: Function;
}
class Watcher implements Observer{
constructor(private name: string) {}
notify() {
console.log(`${this.name} has been notified.`);
}
}
1.2 定义 Subject 类
class Subject {
private observers: Watcher[] = [];
public addObserver(Watcher: Watcher): void {
this.observers.push(observer);
}
public notifyObservers(): void {
console.log("notify all the Watchers");
this.observers.forEach(Watcher => Watcher.notify());
}
}
1.3 使用示例
// 1、创建被观察者
const subject: Subject = new Subject();
// 2、添加观察者
const watcherrA = new Watcher("watcherrA");
const watcherrB= new Watcher("watcherrB");
subject.addObserver(watcherrA);
subject.addObserver(watcherrB);
// 3、通知所有观察者
subject.notifyObservers();
通过示例可以看出,使用观察者模式最简单的需要三个步骤,分别是:创建被观察者、添加观察者、通知观察者。
二、什么是响应式?
响应式:当数据发生变化时能对应做出变化。简单的例子就是我们常用的Excel表格,当我们改变某一项值时, 总能自动计算出我们变化的结果,然后显示出来。
_图片来源 _聊聊 observer-util 这个库,让你读懂响应式原理
_
在前端大多数场景中,我们所观察的目标是数据,当数据发生变化的时候,页面能实现自动的更新,对应的就是我们在页面下数据变更,视图自动更新。
要实现自动更新,我们需要满足两个条件:一个是能实现精准地更新,另一个是能检测到数据的异动。要能实现精准地更新就需要收集对该数据异动感兴趣的更新函数(观察者),在完成收集之后,当检测到数据异动,就可以通知对应的更新函数。
三、Vue自动更新——响应式原理
3.1、Vue中如何准确更新?
在Vue视图中,我们可以通过
v-text = "data.name" 或者差值表达式 {{ data.prop }} 等Vue语法准确知道需要更新的位置在哪里。
3.2、Vue中如何检测到数据的异动?
通过上面例子可以知道,在Vue底层是使用了 Object.defineProperty Api 检查数据的异动(Vue3.0使用的是更强的 Proxy Api,后续再补充Vue3.0响应式原理),这就解决了数据异动的问题。
3.3、Vue响应式核心思路也是这样,创建被观察者、添加观察者、通知观察者。
在Vue中是通过 Observer 类自动完成帮我们完成以上3个核心步骤,在Vue 中被观察者名称换成了 Dep 类。
class Observer {
constructor(data) {
this.walk(data)
}
walk (data) { // 循环执行data
if (!data || typeof data !== 'object') {
return
}
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive (obj, key, val) {
let that = this
this.walk(val)
// 1、创建一个Dep 被观察者
const dep = new Dep()
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get() {
// 2、添加观察者
Dep.target && dep.addSub(Dep.target)
return val // 如果使用obj[key],会变成死循环
},
set(newValue) {
if (newValue === val) {
return
}
val = newValue
that.walk(newValue) // 修改后可能是对象,set函数内部调用,修改了this指向
// 3、通知观察者
dep.notify() // 发送通知
}
})
}
}
当初始化时 Vue 会 data 中对所有的数据做一次遍历,再通过 Object.defineProperty 方法 对数据中的 key 设置 setter "陷阱",每当数据发生变化时,总能触发 setter 方法,这样就能检测到数据异动问题。
3.4、创建被观察者
当我们在使用 new Vue 时,Vue 会在初始化过程中对 data 中的数据进行 "特殊" 处理 defineReactive 方法 对数据中的 key 设置 getter "陷阱",
3.5、添加观察者
每个被观察者是在 初始化数据的时候,通过defineReactive方法 对数据中的key 设置好 getter和 setter "陷阱"。 当用户去访问用 data.product 访问数据时,就会触发之前设置好的 getter "陷阱",然后就自动被添加到观察者集合里面了
3.6、通知观察者
当我们采用 data.product = xxx 修改值时,同理也会自动触发我们在 setter 中设置的 "陷阱"然后通知观察者了。
最后感兴趣的童鞋可以拉下这个项目地址,断点试试看,就清楚了~