首先咱們先本身嘗試實現一下數據監測。所謂數據監測就是當一個值改變時,用到這個值得地方作出相應改變。裏面的核心就是 Object.defineProperty
。數組
var obj = {a: 1}; Object.defineProperty(obj, a, { enumerable: true, configurable: true, get: function(){ // 當調用get方法是,就代表用到了該屬性。 //這邊的問題就卡在 須要收集什麼 } })
咱們收集依賴的目的就是當值改變時,依賴的部分也要作出相應的改變。
可是如今的問題是依賴的地方會有不少,類型也不同。多是template裏面用到,多是computed裏面計算用到。也有可能用戶本身watch監聽。先拋開用到地方應該怎麼變,想一想,一個動做觸發一個事件,不就是回調函數的邏輯嗎?
拿最簡單的watch來講:app
var callback = function (newVal, oldVal) { // do something } vm.$watch(obj.a, callback)
這麼一看就簡單了,咱們收集的依賴就是"回調函數"。
就像上面說的,一個屬性用到的地方可能會有不少,所以須要收集的回調函數也不少,所以咱們用一個數組來保存。
同時呢,這是個通用方法,咱們能夠封裝一下提出來。
所以上面的代碼能夠改爲函數
var obj = {a: 1}; function defineReactive (data, key, val) { var dep = []; Object.defineProperty(obj, a, { enumerable: true, configurable: true, get: function(){ dep.push(callback); // 先無論callback哪來的 }, set: function(newVal){ if(val === newVal) return val = newVal; dep.forEach(function(callback, index){ callback(); }) } }) }
能夠把dep封裝成一個對象oop
class Dep { constructor(id){ this.id = id; this.deps = []; } addSub( sub ){ this.subs.push(sub) } removeSub (sub){ reomve(this.subs, sub) } notify(){ this.deps.forEach(function(callback, index){ callback(); }) } } function defineReactive (data, key, val) { var dep = new Dep(); Object.defineProperty(obj, a, { enumerable: true, configurable: true, get: function(){ dep.addSub(callback); // 先無論callback哪來的 }, set: function(newVal){ if(val === newVal) return val = newVal; dep.notify(); } }) }
上面雖然藉助callback來幫助理解,但真正實現確定不可能真是callback。任何一個函數在不一樣的上下文中執行結果都不相同,光拿到要執行的函數確定不行,還得有執行的上下文。所以咱們可用個類包裝一下,observe中不關心怎麼執行callback,只須要通知一個監聽者本身去作更新操做就好。這個監聽者就是watcher.this
class Watcher { constructors(component, getter, cb){ this.cb = cb // 對應的回調函數,callback this.getter = getter; this.component = component; //這就是執行上下文 } //收集依賴 get(){ Dep.target = this; this.getter.call(this.component) Dep.target = null; } update(){ this.cb() } }
既然咱們將callback換成了Watcher實例,注意這邊,Dep裏面收集的Watcher實例,可不是Wacther構造函數。那麼在數據的getter方法中就要想辦法拿到。咱們將實例存放在Dep中,一個函數對象上。Dep.target = this
,當依賴收集完就銷燬 Dep.target = null
。所以Observe
代碼能夠改爲。.net
function defineReactive (data, key, val) { var dep = new Dep(); Object.defineProperty(obj, a, { // .... get: function(){ if(Dep.target){ dep.addSub(Dep.target); // Dep.target是Watcher的實例 } }, // ... }) } class Dep { //... notify(){ this.deps.forEach(function(watcher, index){ watcher.update(); }) } }
大概流程通了,咱們再作點完善。Observe,Dep, Watcher三個關係弄清楚了。如今的問題是,怎麼收集依賴和回調。舉例來講:prototype
<div id="app"> <input type="text" v-model="name"/> <div>{{name}}</div> </div> new Vue({ el: "#app", data: { name: "默認值", age: 29 } })
name
屬性直接在template中用到。那麼只要觸發render,就能夠收集到依賴。固然,收集到依賴後,須要及時更新。把DOM中的{{name}}
替換成Data
中對應的值。code
這部分代碼在 lifecycle.js
的mountComponent
方法中,能夠精簡爲component
export function mountComponent(){ ... callHook(vm, 'beforeMount') //生命週期函數 var updateComponent = () => { // 先經過render收集依賴,再經過update將虛擬DOM中的值同步到真實節點中 vm._update(vm._render(), hydrating) } new Watcher(vm, updateComponent, emptyFunc, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') //生命週期函數 } } } vm._isMounted = true callHook(vm, 'mounted') //生命週期函數 }
每一個模板實例化一個Watcher實例。這也與官網的流程圖一致
server
<div id="app"> <input type="text" v-model="name"/> <div>{{name}}</div> </div> new Vue({ el: "#app", data: { name: "默認值", age: 29 }, watch: { age: function(newValue, oldValue){ console.log("新的值爲:" + newValue) } } })
傳進來的options會在initState中處理
export function initState (vm: Component) { //vm vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } } ..... function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } } function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options) }
能夠看到對於options
中的watch
其實就是執行$watch
方法。
Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this options = options || {} options.user = true // expOrFn: key, cb: callback watch中的每一個key都實例出Wacther實例 const watcher = new Watcher(vm, expOrFn, cb, options) // immediate: 若是爲true就馬上執行一次,不然第一次進來不執行,當data改變纔會觸發執行 if (options.immediate) { cb.call(vm, watcher.value) } return function unwatchFn () { watcher.teardown() } }
同watch,計算屬性computed也是在initData中處理。
function initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get // 能夠看到,VUE爲每一個computed屬性也都生成了一個watcher實例。 //而這邊的getter就是計算屬性的計算函數。必須先計算一次才能觸發依賴的屬性的get方法,收集依賴 watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions) if (!(key in vm)) { defineComputed(vm, key, userDef) } } }
這邊就再也不放出VUE的數據監聽部分源碼,能夠本身閱讀watcher.js
,dep.js
,observer/index.js
。整體代碼和咱們本身實現的很像,只是比咱們代碼更縝密,多了些其餘功能。好比$watcher以後會返回一個取消函數,能夠取消監聽。
就像上面分析的,一個監聽流程的完成必須包含:
咱們知道VUE爲template,watch,和computed中的屬性實例化了Watcher。而只有在data中的屬性纔會再initState時進行監聽操做。所以咱們能夠得出結論,