观察者模式
观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
vue的响应式原理采用的就是观察者设计模式。下面是观察者模式在vue中的运用解析。
图片来源:https://blog.csdn.net/github_36369819/article/details/79201314
- Observer:监听者,通过Obeject.defineProperty对数据进行劫持,数据变更时通知Dep。
- Dep:订阅器,用一个数组收集watcher依赖,当数据变更时,通知数组中的watcher进行更新。
- Watcher:观察者,收到Dep发来的数据变更通知,获取新数据,更新视图。
下面来看vue中的具体实现。
Observer
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
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.
*/
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
/**
* Observe a list of Array items.
*/
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
最终都会调用walk函数,walk函数又会调用defineReactive$$1。
/**
* Define a reactive property on an Object.
*/
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var 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) {
var 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();
}
});
}
get
在get
中我们会返回value值,同时当Dep.target
存在时会调用dep.depend
,如果有子对象,子对象也调用dep.depend
,是数组也会调用dependArray
方法。这边Dep.target
就是一个watcher
实例,也就是判断当前有没有观察者,如果有则depend
方法会把当前的Dep.target
添加到dep
中的存放watcher
的数组中,这边就是把关联的依赖收集起来了。在下面的Dep
源码中我们会看到depend
方法的具体实现。
set
在set
方法中我们会对value
进行重新赋值,如果新值和旧值则不做任何操作,直接返回。如果有变化会对新值进行observe
监听。同时会调用dep.notify()
方法通知dep收集的依赖进行更新,notify
详细代码请看下面的Dep
源码。
Dep
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
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()
}
}
}
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
depend
depend
方法在上面get
方法中调用,实现很简单,还是判断Dep.target
这个值存不存在,如果存在调用target
的addDep
方法。addDep
的具体实现我们会在Watcher
源码中介绍。
notify
notify
方法中会遍历subs
数组,subs
数组存放的就是一个个watcher
实例,也就是收集到的依赖,调用每一个watcher
的update
方法,该方法的目的就是获取新的变更数据,更新视图。
pushTarget和popTarget
我们看到默认对target
进行赋值为null
,同时有一个targetStack
栈用来存放历史target
,pushTarget
方法的作用是对Dep.target
进行赋值,同时塞入targetStack
栈,popTarget
方法是把当前target
出栈同时把Dep.target
值取栈最上层target
。
Watcher
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
省略...
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
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)) {
dep.addSub(this)
}
}
}
省略...
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
省略...
}
}
constructor
我们着重介绍构造函数的三个参数
- vm: vue实例;
- expOrFn: 表达式或者对象,用于取值触发上面defineReactive中定义的get方法;
- cb: callback,用数据变更时的update中的回调。
构造函数中对getter
进行赋值,如果expOrFn
为Function
直接赋值,如果不是则调用parsePath
对表示进行解析,parsePath
会返回一个函数,该函数会解析expOrFn
表达式进行取值。
/**
* Parse simple path.
*/
var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]"));
function parsePath (path) {
if (bailRE.test(path)) {
return
}
var segments = path.split('.');
return function (obj) {
for (var i = 0; i < segments.length; i++) {
if (!obj) { return }
obj = obj[segments[i]];
}
return obj
}
}
get
构造函数的最后会调用get
方法,我们看下get
的实现,该方法先是调用了上面我们说到的pushTarget
方法把当前watcher
赋值给Dep.target
,当执行this.getter.call(vm, vm)
时会触发exp表达式对应Observer
监听的data
中数据的get
方法。get
方法中会把当前的Dep.taregt
收集依赖。最后调用popTarget
方法释放这个Dep.Target
。
addDep
add
方法我们可以看到最后会调用dep.addSub(this)
,这个可以回到Dep
源码中看到这个方法的实现,就是把当前的watcher
添加到subs
数组中了,看到这边整个依赖的收集就串联起来了。
update和run
update
在dep
的notify
方法中进行调用,这个方法最终会执行run
方法,该方法会调用构造函数中watcher
的回调函数,进行页面数据的更新。
总结
这节我们分析了Vue响应式原理中核心代码的实现,分为Observer,Dep和Watcher三个部分,这三个部分共通实现了观察者模式。大家在看源码时着重分析这三个部分代码可以更好的理解vue的响应式原理,再下一节我们来参照vue的响应式原理,实现一个简易版的vue。
跳转上一篇
来源:CSDN
作者:云之彼端灬约定之所
链接:https://blog.csdn.net/qq_29837295/article/details/103919179