观察者模式又被称为 发布-订阅
模式,这种模式定义了对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并完成自动更新。
从上图可以看到,一个 subject 可以被多个 observer 订阅/观察。当 subject 状态发生变化时,就会通知订阅者进行更新操作。在这样的一个模式中,订阅者的数量以及具体订阅者都是不确定的(订阅者列表是动态变化的),但仍能保证整个机制的正常运转。
Vue.js 实现响应式的核心是利用了 ES5 中的 Object.defineProperty
方法(这也是为什么 Vue.js 不能兼容 IE8 及以下版本浏览器的原因)。
Vue 会为每个组件实例都创建一个 watcher
,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖(依赖收集
)。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染(派发更新
)。
代码实现上: 创建 Vue 实例时,会传入一个 function / object
作为 data 属性。Vue 会遍历 data 属性所对应的对象的属性,并借助 Object.defineProperty
方法,进行数据劫持,将每个数据属性都转化为响应式对象
(拥有setter
和 getter
)。
依赖收集
派发更新
说明: 下文分析的是 vue.js 2
的部分源码。
defineReactive
方法export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// 依赖收集
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
// 派发更新
dep.notify()
}
})
}
dep.depend()
方法说起,下面是 Dep 类中的 depend 方法的实现:depend () {
if (Dep.target) {
// 订阅
Dep.target.addDep(this)
}
}
其中调用了 Watcher 实例中的 addDep 方法:
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// 添加当前 watcher 到依赖列表 subs 中
dep.addSub(this)
}
}
}
dep.notify()
说起,下面是 Dep 类中的 notify 方法的实现:notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
// 遍历依赖列表,通知订阅者更新
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
其中调用了 watcher 实例中的 update 方法:
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
// 将更新 watcher 放入更新队列中,在下一个 tick 调用
queueWatcher(this)
}
}
源码中考虑的情况比较多,看起来比较复杂。为了便于理解观察者模式使用的核心思想,可以暂时忽略分支逻辑,只查看主要的逻辑。下面的代码,就是忽略各种分支情况后,最最简单的一个实现:
// 观察者模式
class Watcher {
constructor(vm, expr, callback) {
this.vm = vm;
this.expr = expr;
this.callback = callback;
this.oldValue = this.get();
}
get() {
Dep.target = this;
let value = CompileUtil.getValue(this.vm, this.expr);
Dep.target = null;
return value;
}
update() {
let newValue = CompileUtil.getValue(this.vm, this.expr)
if (this.oldValue !== newValue) {
this.callback(newValue)
}
}
}
// 发布/订阅
class Dep {
constructor() {
// 存储所有的观察者
this.subs = [];
}
// 订阅
addSub(watcher) {
this.subs.push(watcher);
}
// 发布
notify() {
this.subs.forEach((watcher) => {
watcher.update()
})
}
}
// 实现数据劫持
// 观察类 将传入的数据的定义都改为 defineProperty 的方式
class Observer {
constructor(data) {
this.observe(data);
}
observe(data) {
if (data && typeof data === 'object') {
for (let key in data) {
this.defineReactive(data, key, data[key]);
this.observe(data[key]);
}
}
}
defineReactive(obj, key, value) {
this.observe(value);
let dep = new Dep();
Object.defineProperty(obj, key, {
get() {
// 添加观察者
Dep.target && dep.addSub(Dep.target);
return value;
},
set: (newValue) => {
if (newValue !== value) {
// 将新值转为 get set 形式后再进行赋值
this.observe(newValue);
value = newValue;
// 通知观察者
dep.notify();
}
}
})
}
}