在分析响应式原理时,通过getter/setter方法掌握了数据响应式/数据驱动的时机,但对页面中具体的DOM节点修改还不清楚,这是响应式原理的另一个核心——通知指定DOM节点修改:
- 如何建立视图和数据的关系
- 如何利用两者关系进行交互
1. 揭秘视图与数据间关系
在文章《Vue数据响应式》中,简单示例了getter和setter的功能:
- getter:在进行取值的时候进行附加的操作,比如打印取得值
- setter:在数据发生变化的时候进行一些附加操作,比如通知页面视图更新
这已经可以完成页面的自动更新了,但为了提高页面性能,必须只更新变化的部分,实现最大化的复用。因此需要对数据响应式进行进一步的挖掘,即充分利用getter和setter。
- 页面视图中使用数据时,会调用数据的getter方法;
- getter方法中可以将使用数据的页面元素收集起来,作用是等到数据改变时可以知道哪些页面元素需要改变;
- 数据变化时会触发数据的setter方法;
- setter方法可以通知收集的页面元素做出改变。
因此需要一个存储页面元素的对象,同时这个对象能够响应setter,从已有的23种设计模式中检索,发现发布订阅(观察者)模式,非常适合这个场景。
2. 设计模式简介
如今各种编程语言(如java、python、c++、c、JavaScript等)各领风骚,但在计算机软件开发领域的深处,实际上有一套非常成熟的规范或说明书,它就是设计模式,它的存在总结并统一了特定应用场景下的核心处理思想,本节从设计模式的定义和设计模式的基本内容进行展开介绍。
2.1 辨识设计模式
- 设计模式的概念:一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验总结。
- 设计模式的目的:
- 代码的可重用性
- 代码的可读性
- 代码的可靠性
- 设计模式的优点:
- 便于协作
- 实现代码编制工程化
- 个人认识:
- 设计模式的核心是意图而不是实现方式或代码结构***
- 设计模式是面向应用场景的
- 设计模式不是万能的
2.2 设计模式分类
一般优秀的框架都有优秀的逻辑实现方式,而这些实现方式总结、汇聚之后形成了目前经典的23种编程设计模式,而这么多的编程范式中又可以分成三大类,即创建型模式(对象实例化的模式)、结构型模式(将类或对象结合成更大的结构)、行为型模式(类和对象如何交互以及划分责任和算法),如下图所示
3. vue中的观察者模式应用
观察者模式的核心是触发联动,适用于一对多的依赖关系,实现多个对象同时监听一个主体,主体的状态变化会导致多个对象自动更新自己。它在Vue中应用场景就是数据响应式。
但在vue中应用有两处,一是有两个主体(Watcher和Sub)参与的数据响应式;一个是有三个主体参与的事件注册。
3.1 观察者模式——两个主体的直接交互
3.1.1 应用场景再现
let dep = new Dep(); // 目标/发布者
let watcher = new Watcher(); // 观察者/订阅者
dep.addSub(watcher); // 注册,建立两个主体的交互通道
dep.notify(); // 开始交互
3.1.2 实现模拟
分析结果:
- 存在两个对象 Dep和Watcher
- 首先需要注册, 然后才能交互
- 观察者/订阅者: Watcher
- update(): 当事件发生时, 具体要做的事情
- 目标/发布者:Dep
- subs数组: 存储所有的watcher
- addSub():添加watcher
- notify():当变化发生后,调用所有观察者的update方法
// Dependency 目标(发布者)
class Dep{
constructor(){
this.subs = []; // 存储所有的观察者
}
addSub(sub){ // 添加观察者
if(sub && sub.update){
this.subs.push(sub);
}
}
notify(){ // 通知所有的观察者
this.subs.forEach(sub => {
sub.update();
})
}
}
// 观察者(订阅者)
class Watcher{
update(){
console.log("update"); // 进行更新操作
}
}
3.1.3 总结
从租房经历分析观察者模式:
- 发布者/目标:租房中介
- 观察者/订阅者:关注房源的人(潜在租客)
- 房源状态改变后,中介会通知潜在租客们
3.2 发布订阅模式——两个主体借助第三方主体的间接交互
3.2.1 应用场景再现
const bus = new Vue();
bus.$on("dataChange", () => {
console.log("datachange");
})
bus.$on("dataChange", () => {
console.log("datachange-another one");
});
bus.$emit("dataChange");
3.2.2 实现模拟
分析结果:
- 存在一个对象
- 这个对象上面 o n 和 on和 on和emit方法
- 通过$emit发布消息, 则存在一个发布者
- 通过$on订阅消息,则存在一个订阅者
- 发布者和订阅者之间的桥梁就是一个特殊对象——信号中心
class EventEmitter{
constructor(){
// eventType:[]
this.subs = {};
}
// 订阅通知
$on(eventType, handler){
this.subs[eventType] = this.subs[eventType] || [];
this.subs[eventType].push(handler);
}
// 发布通知
$emit(eventType){
if(this.subs[eventType]){
this.subs[eventType].forEach(handler => {
handler();
})
}
}
}
3.2.3 总结
还是从租房经历分析这个模式:
- 信号中心: 租房中介
- 接收并记录潜在租户的注册信息
- 向潜在租户发送租房信息
- 订阅者:潜在租户
- 向租房中介注册租房需求
- 发布者:房东
- 房东有空闲的房子,提供给租房中介房源;
- 让租房中介发送租房信息
3.3 对比
- 观察者模式是由具体目标调度,比如 当事件触发,Dep会去调用观察者的方法,所以观察者模式的订阅者和发布者之间存在依赖的,即强耦合关系。因此Vue视图和数据间采用这种方式。
- 发布/订阅模式是由统一调度中心调用,因此发布者和订阅者之间不需要知道对方的存在,即弱耦合关系。组件间通信和自定义事件采用的是这种方式。
4. 总结
本篇重点分析了视图和数据的关系,并为了解决视图不全局更新的痛点,分析了观察者设计模式的两种实现方式。
后续将模拟Vue响应式原理,从Vue的基本结构、数据劫持、解析指令、发布者、观察者五个角度实现Vue的整个过程。