浅析从vue源码看观察者模式
观察者模式首先话题下来,我们得反问一下自己,什么是观察者模式? 概念观察者模式(Observer):通常又被称作为发布-订阅者模式。它定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖于它的对象都会得到通知并自动更新,解决了主体对象与观察者之间功能的耦合。 讲个故事上面对于观察者模式的概念可能会比较官方化,所以我们讲个故事来理解它。 A:是共产党派往国民党密探,代号 001(发布者) B:是共产党的通信人员,负责与 A 进行秘密交接(订阅者)
适用性以下任一场景都可以使用观察者模式
vue 对于观察者模式的使用vue 使用到观察者模式的地方有很多,这里我们主要谈谈对于数据初始化这一块的。 1、实现数据劫持上图我们可以看到,vue 是利用的是 Object.defineProperty() 对数据进行劫持。 并在数据传递变更的时候封装了一层中转站,即我们看到的 Dep 和 Watcher 两个类。 这一小节,我们只看如何通过观察者模式对数据进行劫持。 1.1、递归遍历我们都知道,vue 对于 data 里面的数据都做了劫持的,那只能对对象进行遍历从而完成每个属性的劫持,源码具体如下 1.2、发布/订阅从上面对象的遍历我们看到了 defineReactive ,那么劫持最关键的点也在于这个函数,该函数里面封装了 getter 和 setter 函数,使用观察者模式,互相监听 1.3、返回 Observer 实例上面我们看到了observe 函数,核心就是返回一个 Observer 实例 2、消息封装,实现 "中转站"首先我们要理解,为什么要做一层消息传递的封装? 我们在讲解观察者模式的时候有提到它的 适用性 。这里也同理,我们在劫持到数据变更的时候,并进行数据变更通知的时候,如果不做一个"中转站"的话,我们根本不知道到底谁订阅了消息,具体有多少对象订阅了消息。 这就好比上文中我提到的故事中的密探 A(发布者) 和共产党 B(订阅者)。密探 A 与 共产党 B 进行信息传递,两人都知道对方这么一个人的存在,但密探 A 不知道具体 B 是谁以及到底有多少共产党(订阅者)订阅着自己,可能很多共产党都订阅着密探 A 的信息,so 密探 A(发布者) 需要通过暗号 收集到所有订阅着其消息的共产党们(订阅者),这里对于订阅者的收集其实就是一层封装。然后密探 A 只需将消息发布出去,而订阅者们接受到通知,只管进行自己的 update 操作即可。 简单一点,即收集完订阅者们的密探 A 只管发布消息,共产党 B 以及更多的共产党只管订阅消息并进行对应的 update 操作,每个模块确保其独立性,实现高内聚低耦合这两大原则。 废话不多说,我们接下来直接开始讲 vue 是如何做的消息封装的 2.1、DepDep,全名 Dependency,从名字我们也能大概看出 Dep 类是用来做依赖收集的,具体怎么收集呢。我们直接看源码 export default class Dep {
static target: ?Watcher; id: number; subs: Array constructor () { // 收集订阅者 depend () { notify () { // the current target watcher being evaluated. 代码很简短,但它做的事情却很重要
源码中,还抛出了两个方法用来操作 Dep.target ,具体如下 export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target) // 改变目标指向 Dep.target = _target } export function popTarget () { 2.2、 WatcherWatcher 意为观察者,它负责做的事情就是订阅 Dep ,当Dep 发出消息传递(notify)的时候,所以订阅着 Dep 的 Watchers 会进行自己的 update 操作。废话不多说,直接看源码就知道了。 constructor (
vm: Component,expOrFn: string | Function,cb: Function,options?: Object ) { this.vm = vm vm._watchers.push(this) this.cb = cb // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { // 解析表达式 this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} } } this.value = this.get() } get () { let value = this.getter.call(vm,vm) return value // 订阅 Dep,同时让 Dep 知道自己订阅着它 // 订阅者'消费'动作,当接收到变更时则会执行 run () { 上述代码中,我删除了一些与目前探讨无关的代码,如果需要进行详细研究的,可以自行查阅 vue2.5.3 版本的源码。 现在再去看 Dep 和 Watcher,我们需要知道两个点
两者看似相互依赖,实则却保证了其独立性,保证了模块的单一性。 更多的应用vue 还有一些地方用到了"万能"的观察者模式,比如我们熟知的组件之间的事件传递,$on 以及 $emit 的设计。 $emit 负责发布消息,并对订阅者 $on 做统一消费,即执行 cbs 里面所有的事件。 ,fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0,l = event.length; i < l; i++) {
this.$on(event[i],fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
}
return vm
}
Vue.prototype.$emit = function (event: string): Component { 总结本文探讨了观察者模式的基本概念、适用场景,以及在 vue 源码中的具体应用。这一节将总结一下观察者模式的一些优缺点
OK,本文到这就差不多了,更多的源码设计思路细节将在同系列的其它文章中进行一一解读。 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |