前言
在面试题中常常会呈现与“公布订阅”模式相干的题目,比方考查咱们对 Vue 响应式的了解,也会有题目间接考验咱们对“公布订阅”模式或者观察者模式的了解,甚至还会有一些手写算法题。笔者就在往年三月加入某平安公司的面试时被要求手写代码实现“公布订阅”模式,过后因为没有筹备没有答复上来,悔不该当初。由此可见“公布订阅”模式是一个十分重要的设计模式,接下来咱们一起学习下吧。
观察者模式 vs“公布订阅”模式
首先须要廓清的是,这两者尽管类似,却有不同。
观察者模式
只波及两个要害角色,发布者与订阅者。观察者模式定义了一种 一对多 的依赖关系,让多个观察者对象同时监听某一个指标对象,当这个指标对象的状态发生变化时,会告诉所有观察者对象,使它们可能自动更新。
发布者的行为:
- 减少订阅者
- 移除订阅者
- 告诉所有订阅者
代码实现如下:
// 定义发布者类
class Publisher {constructor() {
// 创立订阅者
this.observers = []}
// 减少订阅者
add(observer) {this.observers.push(observer)
}
// 移除订阅者
remove(observer) {this.observers.map((item, i) => {if (item === observer) {this.observers.splice(i, 1)
}
})
}
// 告诉所有订阅者
notify() {this.observers.map((observer) => {observer.update(this)
})
}
}
订阅者行为:
- 被告诉
- 去执行
代码实现:
// 定义订阅者类
class Observer {constructor() {console.log('创立订阅者')
}
update() {console.log('订阅者更新')
}
}
“公布订阅”模式
与观察者模式相似,然而在这种模式下发布者齐全不必感知订阅者,不必关怀它怎么实现回调办法,事件的注册和触发都产生在独立于单方的第三方平台(事件总线)上。公布 - 订阅模式下,实现了齐全地解耦。
vue2.x 响应式原理探索
响应式实现过程官网解释
- 在 Vue data 中存入对象数据
- Vue 遍历此对象每个 property,通过 Object.defineProperty()办法进行数据劫持,给每一个 property 加上 getter/setter
- 每个组件实例都对应一个 Watcher 实例,会收集所有接触过的 property 依赖,如果数据有变动,将会收到告诉,watcher 会使其关联的组件实例扭转
深刻了解响应式原理 官网文档 v2
公布订阅模式在 vue 响应式原理中的具体使用
初始化过程
class Vue {constructor(options) {
this.$options = options;
this.$data = options.data;
// 对 data 选项做响应式解决
observe(this.$data);
// 代理 data 到 vm 上
proxy(this);
// 执行编译
new Compile(options.el, this);
}
}
发布者 / 伪订阅者:Observe
observe 在前文中代表了订阅者,但这里他更多的是一个发布者,执行了发布者的权力,减少了订阅者,并且在扭转时告诉了订阅者。
function observe(obj) {if (typeof obj !== "object" || obj == null) {return;}
new Observer(obj);
}
class Observer {constructor(value) {
this.value = value;
Object.keys(value).forEach((key) => {defineReactive(value, key, value[key]);
});
}
}
defineReactive
是一个十分重要的办法,为每⼀个 key
创立⼀个 Dep
实例
function defineReactive(obj, key, val) {
// 递归遍历 确保深层次 key 也可能响应
this.observe(val);
// 对每个 key 都建设一个 Dep 管家
const dep = new Dep();
// 应用 defineProperty 对每个 key 建设 getter/setter
Object.defineProperty(obj, key, {get() {Dep.target && dep.addDep(Dep.target);// Dep.target 也就是 Watcher 实例
return val;
},
// 监听变动
set(newVal) {if (newVal === val) return;
// 告诉 dep 执行更新办法
dep.notify();},
});
}
事件核心:Dep 管家,治理实在订阅者 Wacther
Dep 收集了组件实例中同一个 key 对应的所有订阅者 Wacther
// 发布者
class Dep {constructor() {this.deps = []; // 依赖治理
}
addDep(dep) {this.deps.push(dep);
}
notify() {
// 实际上是调用的 watcher 中的更新事件
this.deps.forEach((dep) => dep.update());
}
}
理论的订阅者:Watcher
通过触发 get, 将 watcher 增加到 key 对应的 Dep 中
// 订阅者 负责更新视图
class Watcher {constructor(vm, key, updater) {
this.vm = vm
this.key = key
this.updaterFn = updater
// 创立实例时,把以后实例指定到 Dep.target 动态属性上
Dep.target = this
// 读一下 key,触发 get 便将 watcher 增加到 key 对应的 Dep 中
vm[key]
// 置空
Dep.target = null
}
// 将来执行 dom 更新函数,由 dep 调用的
update() {this.updaterFn.call(this.vm, this.vm[this.key])
}
}
VUE2.X 响应式的局限性
因为 js 的限度,Vue 不能检测数组和对象的变动。尽管如此咱们还是有一些方法来回避这些限度并保障它们的响应性。
对象
Vue 无奈检测 property 的增加或移除,因为 Vue 会在 初始化实例 时对 property 执行 getter/setter 转化,所以只有在初始化实例时就存在于 data 的 property 才是响应式的。
对于曾经创立的实例,Vue 不容许动静增加根级别的响应式 property。然而,能够应用 Vue.set(object, propertyName, value)
办法或者其别名 vm.$set
向嵌套对象增加响应式 property。例如:
Vue.set(vm.someObject, 'b', 2)
this.$set(this.someObject,'b',2)
为已有对象赋值多个新 property,需用原对象与要混合进去的对象的 property 一起创立一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2})`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2})
数组
Vue 不能检测以下数组的变动:
- 当你利用索引间接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你批改数组的长度时,例如:
vm.items.length = newLength
为了解决第一类问题,以下两种形式都能够实现和 vm.items[indexOfItem] = newValue
雷同的成果,同时也将在响应式零碎内触发状态更新:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
你也能够应用 vm.$set
实例办法,该办法是全局办法 Vue.set
的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
为了解决第二类问题,你能够应用 splice
:
vm.items.splice(newLength)
感激
本篇文章探讨了公布订阅模式以及观察者模式,同时也探讨了公布订阅模式在 vue2.x 响应式中的利用,如有不对,欢送斧正,下一篇文章我将持续探索 vue3.x 的响应式原理,如果感觉写的还行就帮我点个赞吧,这样我会更有能源进行接下来的常识输入!谢谢各位朋友的观看!