這一篇主要是講解一下vue裏mvvm的原理,以及如何理解vue實現mvvm。vue
稍微有去了解過vue是如何雙向綁定的咱們都很容易知道vue是經過Object.defineProperty
劫持data
屬性的setter
和getter
,可是這僅僅只是實現的一部分,在這個實現裏咱們還要理解dep
(訂閱中心)和watcher
(訂閱者)的概念。express
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
:bash
watcher
watch
選項中聲明的watcher
watcher
講完watcher
的來源後咱們再來看這幾個方法的講解:async
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
的理解,要明白這兩個類的實例在雙向綁定的過程當中扮演的是一個什麼樣角色,單純從代碼上可能不太容易理解這樣設計的意圖,可是若是能有一個比較具象化的東西來對應,相信對你的理解會有很是大的幫助。mvvm