内容简介:这一篇主要是讲解一下vue里mvvm的原理,以及如何理解vue实现mvvm。稍微有去了解过vue是如何双向绑定的我们都很容易知道vue是通过
这一篇主要是讲解一下vue里mvvm的原理,以及如何理解vue实现mvvm。
稍微有去了解过vue是如何双向绑定的我们都很容易知道vue是通过 Object.defineProperty
劫持 data
属性的 setter
和 getter
,但是这仅仅只是实现的一部分,在这个实现里我们还要理解 dep
(订阅中心)和 watcher
(订阅者)的概念。
dep
—订阅中心
dep
代码在 ./src/core/observer/dep.js
文件里,下面简单讲解一下:
-
dep
的定义参考了观察者设计模式,每一个dep
有自己的唯一标识id
和订阅者列表subs
。 -
addSub
和removeSub
用来管理订阅者列表subs
。 -
depend
用来收集watcher
(订阅者)。 -
notify
用来通知watcher
(订阅者)执行更新。 -
Dep.target
刚开始看是比较难理解的一个概念,Dep.target
其实是调用当前dep
对应属性的watcher
。举个例子:假如data
有个属性name
,那么当data.name
的getter
被触发时,我们需要知道是谁在调用这个data.name
的getter
,这就是Dep.target
。
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] } 复制代码
watcher
—订阅者
watcher
代码在 ./src/core/observer/watcher.js
文件里,关于 watcher
的选项配置就不细说了,在这里我们只需要重点关注其中的 get
、 update
、 run
、 evaluate
这几个方法。
这几个方法的作用稍后解释,现在我们要先理解怎样才会产生一个 watcher
。在vue里面,有三种类型的 watcher
:
-
每一个组件的实例都是一个
watcher
-
在组件的
watch
选项中声明的watcher
-
计算属性所使用的依赖值会给对应的依赖值添加一个
watcher
讲完 watcher
的来源后我们再来看这几个方法的讲解:
-
先从
update
讲起,当某个响应属性发生变化时触发setter
后,执行dep.notify
通知每个watcher
执行update
,代码比较简单,三个逻辑分支,判断this.lazy
,这是应用于计算属性时会触发的逻辑分支,this.sync
则用于判断同步执行watcher
的回调,否则推入queueWatcher
后续执行。 -
run
和evaluate
都是会调用get
方法,只是run
方法是用于组件实例的watcher
和watch
选项中声明的watcher
,watch
选项中声明的watcher
的this.user
为true
,在run
方法中的this.cb.call(this.vm, value, oldValue)
这段代码则是我们watch
选项中触发的回调。至于evaluate
方法则更加简单了,调用get
方法然后设置this.dirty
为false
则是为了后续其他地方使用这个计算属性的时候不需要重新计算,这也是计算属性缓存的一部分逻辑。 -
接下来讲讲
get
方法,pushTarget(this)
这段则是设置Dep.target
为当前watcher
实例,其实就是告诉dep
是谁在获取属性。value = this.getter.call(vm, vm)
则是获取当前值,在这里三种类型的watcher
的getter
是不一样的。 -
最后提一下,计算属性的值一般是在组件实例的
watcher
执行getter
的过程中执行计算的。
watcher类型 | getter |
---|---|
组件实例 | render函数 |
watch | 执行parsePath方法生成的函数 |
计算属性 | 执行createComputedGetter方法生成的函数 |
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 ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // 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) } } } /** * Clean up for dependency collection. */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** * 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) } } } } /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ evaluate () { this.value = this.get() this.dirty = false } /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } } 复制代码
总结
其实学习vue的mvvm,重点在于 dep
和 watcher
的理解,要明白这两个类的实例在双向绑定的过程中扮演的是一个什么样角色,单纯从代码上可能不太容易理解这样设计的意图,但是如果能有一个比较具象化的东西来对应,相信对你的理解会有非常大的帮助。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。