观察者模式

class Observer {
constructor() {
}
update(val) {
}
}
class ObserverList {
constructor() {
this.observerList = []
}
add(observer) {
return this.observerList.push(observer);
}
remove(observer) {
this.observerList = this.observerList.filter(ob => ob !== observer);
}
count() {
return this.observerList.length;
}
get(index) {
return this.observerList[index];
}
}
class Subject {
constructor() {
this.observers = new ObserverList();
}
addObserver(observer) {
this.observers.add(observer);
}
removeObserver(observer) {
this.observers.remove(observer);
}
notify(...args) {
let obCount = this.observers.count();
for (let index = 0; index < obCount; index++) {
this.observers.get(i).update(...args);
}
}
}
发布订阅模式

class PubSub {
constructor() {
this.subscribers = {}
}
subscribe(type, fn) {
if (!Object.prototype.hasOwnProperty.call(this.subscribers, type)) {
this.subscribers[type] = [];
}
this.subscribers[type].push(fn);
}
unsubscribe(type, fn) {
let listeners = this.subscribers[type];
if (!listeners || !listeners.length) return;
this.subscribers[type] = listeners.filter(v => v !== fn);
}
publish(type, ...args) {
let listeners = this.subscribers[type];
if (!listeners || !listeners.length) return;
listeners.forEach(fn => fn(...args));
}
}
let ob = new PubSub();
ob.subscribe('add', (val) => console.log(val));
ob.publish('add', 1);

观察者模式和发布订阅模式
- 观察者模式中,观察者被订阅者通知,订阅者同时也维护了观察者的一个记录。发布订阅模式下发布者和订阅者互不相识,他们只是在消息队列或者代理的帮助下进行通信
- 发布订阅模式下组件的耦合程度比观察者模式更松
- 观察者模式一般是同步的实现方式,发布订阅模式一般是异步的实现方式
- 观察者模式需要在单个应用程序中实现,而发布订阅模式更多的是跨应用模式下使用。
defineProperty
function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
VUE原理
Vue能够让一个数据变更时视图就进行刷新,而且用到这个数据的其他地方也会同步变更。
所以需要考虑两个问题
- 视图模板
template、computed、watch里用了哪些数据,因为这些数据是需要响应式(reactive)的。 - 数据变更了怎么告诉
render函数。
解决方案:
- 模板渲染的时候要用到某个数据,必然要访问它,所以可以通过
getter拦截,然后做出相应的处理。 - 同时在值变更的时候,可以通过
setter拦截,从而告诉render函数进行重新计算
数据响应式(observer,defineReactive )数据拦截getter、setter
** Observer Dep Watcher **
Observer会将数据响应式,并且每个数据都会有一个Dep实例Dep实例中有个subs队列,保存着依赖本数据的观察者(Watcher对象实例),dep.notify()会通知观察者。
Observer对象针对数组和普通对象进行响应式,生产每个组件的Component类的构造函数里,会进行数据的响应式处理,watcher的实例化(依赖收集)。
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor(value: any) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
<!--将Observer实例挂载到对象或数组的__ob__上,提供后续观测数据使用-->
<!--只为对象或数组 实例一个Observer类的实例,而且就只会实例化一次-->
def(value, "__ob__", this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk(obj: Object) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
// 但是每个key都会响应式处理,每个key都有一个dep实例
defineReactive(obj, keys[i]);
}
}
/**
* Observe a list of Array items.
*/
observeArray(items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
}
}
/**
* Define a reactive property on an Object.
*/
// 在对象上把属性响应式
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) {
// 收集依赖
// template被编译后,会形成AST,在执行render()函数过程中就会触发data.a的getter,并且这个过程是惰性收集的
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();
},
});
}
vue 是如何监听数组的
问题:数组的一些方法,比如(push,unshift)会改变数组的值,但是无法在set中被拦截,所以无法通知更新。
解决办法:Vue针对那些不会被set拦截的方法进行重写。用AOP的思想,先把数组原来方法执行一遍拿到结果result,并且对入参进行监听,同时notify变更,返回result。
hasProto = '__proto__' in {}
/**
* Define a property.
*/
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
增强数组的方法,让数组的方法调用时会去notify观察者
const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
];
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method];
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args);
const ob = this.__ob__;
let inserted;
switch (method) {
case "push":
case "unshift":
inserted = args;
break;
case "splice":
inserted = args.slice(2);
break;
}
if (inserted) ob.observeArray(inserted);
// notify change 重点
ob.dep.notify();
return result;
});
});
保留数组原来的操作 - push、unshift、splice这些方法,会带来新的数据元素,而新带来的数据元素,我们是有办法得知的(即为传入的参数) - 那么新增的元素也是需要被配置为可观测数据的,这样子后续数据的变更才能得以处理。所以要对新增的元素调用observer实例上的observeArray方法进行一遍观测处理 - 由于数组变更了,那么就需要通知观察者,所以通过ob.dep.notify()对数组的观察者watchers进行通知
依赖收集
Observer Dep Watcher
依赖收集器:const dep = new Dep()
观察者怎么观察:this._watcher = new Watcher(this, render, this._update)
-
记录了某个key被哪些watcher watch了,哪些watcher订阅了key,它还有一些对添加、删除watcher的方法。
-
它有一个静态属性 target ,是一个 watcher , 这是全局唯一的 watcher ,因为同一时间只能有一个全局 watcher 被计算。
-
执行mountComponent函数中,传入uddateComponent函数,实例化watcher,此时进入到watcher方法中,执行constructor,执行this.get(),首先pushTarget(this)
-
Observer的构造函数先对data作类型校验和__ob__属性检测,没有__ob__时在对data实例化一个Observer,返回该实例 -
vue将data初始化为一个Observer并对对象中的每个值,重写了其中的get、set,data中的每个key,都有一个独立的Dep实例。 -
在
get中,向依赖收集器添加了监听,dep.depend() -
在
mount时,实例了一个Watcher,将收集器的目标指向了当前Watcher -
在
data值发生变更时,触发set,触发了依赖收集器中的所有监听的更新,来触发Watcher.update
派发更新
- setter派发更新dep.notify()
- watcher.update
- queueWatcher
- flushSchedulerQueue