都知道vue中實現了數據和視圖的雙向綁定,但具體是如何實現的呢?
今天就說說 我閱讀的vue2中的代碼實現的個人所得,如果說錯了,歡迎指正。
注:我閱讀的vue2代碼的版本是v2.2.6,且都是以單文件的方式展示列子, 可以結合下一篇續給出的實際例子結合起來看,更容易理解
話不多說,首先上結論,然后再通過圖以及代碼進行詳細解釋
結論:m->v: 利用觀察者模式,當數據改變的時候通知其觀察者進行更新,
v->m: 利用事件綁定對數據進行設值
實現過程:通過Object.defineProperty的方法定義 get和set方法對數據的操作擁有代理權,並且每個屬性都會有一個對應的依賴,
每個vm組件都會有一個或多個watcher,當獲取vm或者data的數據的時候就會調用get方法,如果這個時候有watcher正在運行觀察,
那么就把這個依賴和此watcher都加入到各自的對象下的相關隊列中去
1)model到view:當設置vm或者data對象的數據的時候,就會調用set方法,
如果這次結果 和上次的value不同,就讓此依賴中的所有watcher加入到scheduler(調度者)的更新隊列中去,
等待nextTick更新scheduler的隊列,調用watcher的update方法,然后運行getter方法比較value值和之前保存的value值是否相同,
如果不同,則調用cb(回調函數),對應vm來說getter是vm.update(vm.render()),當調用getter的時候,整個過程就完成了model到view的更新了
2:view到model:一般用v-model方法進行實現,默認的是綁定dom組件的input事件(這個看源碼的實現,如果你設置option.model.event的值的話,
事件就會變成你設置的值),當觸發此事件的時候,會調用你綁定的值進行設置,就又回到1)過程了,當然也可以自己綁定自己關心的事件實現設值的效果
上圖:
可以從結論中看到,其中有3個比較重要的部分,data對應的dep和vm對應的watcher以及把它們進行關聯的Object.defineProperty定義的get和set方法,
而實現這一切的基礎就是調用Object.defineProperty對屬性設置get和set代理方法,所以vue2只能支持ie8以上,因為ie8及一下沒有這個方法,也沒法用shim的方式來實現
詳細解釋
一. watcher
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: Set; newDepIds: Set; getter: Function; value: any;
constructor (vm: Component, expOrFn: string | Function, cb: Function, options?: Object) {}
run(){} get(){} update(){} addDep(){} cleanupDeps{} evaluate(){} teardown(){} }
注:為了避免太長不方便看,方法的具體實現就沒有帖出來了,如果有興趣的可以去源碼看看
簡稱觀察者,通常意義的解釋,就是對某些事物感興趣而去觀察他的人,用在vue2里面就是對一些表達式結果感興趣而進行觀察的對象,
用來統計更新vm的updateComponent方法,computed屬性或者watch屬性中的expOrFn(表達式或者方法)
watcher對象有3個比較重要的屬性:
getter:關心的表達式或者方法
value: 運行getter的結果,在new watcher的時候如果lazy為false就會調用getter一次來初始化這個值
cb:當運行getter方法得到的value值和之前的value值不同的時候,會調用cb,對於vm組件來說就是更新組件
還有一些其他屬性:
id: watcher的唯一標示,當對一個周期內的所有watcher執行更新的時候,會先根據這個id 從小到大進行排序,而且父級的vm的對應的id總是小於子集
lazy:是否懶更新, 也就是當它關注的表達式中有相關數據改變的時候,它才更新,不然就一直用value值,像computed中的watcher的lazy就是true
dirty:是否已經臟了,如果臟了,當獲取這個值的時候就重新調用getter獲得新值,否則直接返回value的值,和lazy有關系
user: cb回調函數是否是用戶傳進來的,如果是的話執行這個回調就會用try catch的方式,以避免出錯
active: 這個watcher是否是活動的,如果是活動才會去運行getter
deps:對應的所有依賴,是一個數組
newDeps:這次更新后,收集到的下一輪更新所相關的依賴
depIds:所有依賴的id,是一個set對象
newDepIds: 下一輪的依賴id所對應的set對象
注:這里面的deps的作用個人覺得是:當vm銷毀的時候或者運行了一此get方法的時候,從相關的依賴中刪除自己,當某些需要的時候,重新和依賴關系,
目前只看到computed的watcher 在獲取值的時候,如果watcher的dirty為true,就會重新和deps里面的依賴重新建立關系,其他地方就沒看到有使用了- -!
先上一個例子,下面會用到:
<template> <div id="app" class="app"> <label>姓名:</label> <input :value="name" @input="name = $event.target.value"> <select v-model="sex"> <option value="1">男</option> <option value="0">女</option> </select> <p>{{infoName}}</p> <p>{{infoSex}}</p> </div> </template> <script> export default { data(){ return { name:"", sex:1, resultObj:{ allInfo:"" }, } }, computed:{ infoName(){ return "您的姓名是:" + this.name; }, infoSex(){ return "您的性別是:" + (this.sex == 1 ? "男":"女"); } }, watch:{ sex:function(newSex){ this.resultObj.allInfo = this.name + "|" + this.sex; alert("你的性別已經改為"+ (this.sex == 1 ? "男":"女")); }, name:function(newName){ this.resultObj.allInfo = this.name + "|" + this.sex; alert("你的名字已經改為"+ newName); }, "resultObj.allInfo":function(newInfo){ alert("你新的的所有信息為"+ newInfo); } }, } </script>
注: 上述代碼是為了展示 watcher而強行拼湊的代碼,所以有重復的地方,不要在意這些細節
有3個地方會有對應的watcher
1.一個vm組件會對應一個watcher,這個watcher關注的是vm的_render方法,每個vue文件的template里面的內容都會被編譯成一個_render方法,
並且里面用到的屬性或者computed都會轉化成_vm.name 或者_vm.infoName 的代理形式,具體的轉化過程在dep里面說到
2.computed對象里面的每個屬性,都有一個對應的watcher,如上例:infoName,infoSex都會有一個對應的watcher,並且生成的watcher的lazy為true,
即它們都是懶更新的,只有里面用到的相關數據出現變化的時候,這個watcher才會執行getter方法
3.watch對象里面的每個屬性,都有一個對應的watcher,如上例:sex,name,"resultObj.allInfo"都會有一個對應的watcher
"resultObj.allInfo" 這個對應的watcher需要特殊說明下它的getter,先看代碼
if (typeof expOrFn === 'function') { this.getter = expOrFn; } else { this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = function () {}; } //////////////////////////////////////// var bailRE = /[^\w.$]/; 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 } } ///////////////////////////////////////////////
function get(){
if (this.user) { try { value = this.getter.call(vm, vm); } catch (e) { handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\"")); } } else { value = this.getter.call(vm, vm); }
}
如果初始化watcher的時候傳的expOrFn 是function,那么這個getter就直接是這個function,否則就就行轉化,運行parsePath方法得到一個function
當然這個function有一個判斷,是路徑形式的才行,當運行getter方法的時候,傳入vm作為obj,那么其實resultObj.allInfo 得到的是 vm.resultObj.allInfo的值,
當然 不可能直接vm["resultObj.allInfo"]或者vm."resultObj.allInfo"這樣得到,所以用了一個循環來實現
這3種對應的watcher創建的時候分別為:
1) 使用$mount方法的時候
Vue$3.prototype.$mount = function (el, hydrating) { el = el && inBrowser ? query(el) : undefined; return mountComponent(this, el, hydrating) }; function mountComponent (vm,el,hydrating) { var updateComponent = function () { vm._update(vm._render(), hydrating); }; vm._watcher = new Watcher(vm, updateComponent, noop); }
注:1.有些地方我是從分模塊實現的源碼中拷貝過來的,有些是直接是為了方便查找,就直接在vue.common.js中拷貝過來,所以可能代碼風格有些不一樣,
而且為了更方便理解,會對某些地方做小小的改動,會刪除一些和本話題無關的代碼實現過程
2.hydrating 這個屬性和vdom(虛擬dom)實現有關系,在這里暫時不去解釋它的作用, 單詞意思為:保濕
通過實現可以看出來,watcher的getter 就是vm的render方法(update是更新組件,和數據沒有關系,所以把getter轉化為render),每個vm都有自己獨立對應的一個watcher,
這個watcher關心的是整個組件的渲染
上面說的是 在使用$mount方法的時候 會創建這個watcher,但是有時候,我們在創建組件的時候,沒有使用$mount ,比如
const app = new Vue({ el:"#app" router: router, store:store, render: h => h(App) });
這個時候就沒有用$mount,那么它就不會有watcher了嗎?
先上代碼:
function Vue (options) { this._init(options) } Vue.prototype._init = function (options?: Object) {
//...其他初始化實現
if (vm.$options.el) { vm.$mount(vm.$options.el) } }
注: 我吧_init方法里面不相關的代碼都刪除了,只留下了我們關心的地方
在new vue的時候,我們傳入的對象有個el屬性,關鍵就在這里,而在vm初始化的時候會判斷傳進來的對象有沒有這個屬性,如果有就會自動去調用$mount方法,而不需要手動
當然如果又沒有el,又沒有手動調用$mount方法,那么這個組件就不會出現在view上,也不會創建對應的watcher
2.解析computed屬性的時候
Vue.prototype._init = function (options?: Object) { initState(vm) } export function initState (vm: Component) { 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) initWatch(vm, opts.watch) } function initComputed (vm: Component, computed: Object) { const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] let getter = typeof userDef === 'function' ? userDef : userDef.get // create internal watcher for the computed property. watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions) } }
注:1.把有些和此話題不相關的代碼都刪除了,所以帖出來的代碼不是完整的
2.在逛vue論壇的時候,遇到有人提問:在prop屬性設置default為methods里面的方法為什么報空,大家看看initState的初始化順序就明白了
3.data,computed,watch等都會存儲在vm.$option中
在解析computed屬性的時候,會對里面的每個屬性都會有創建一個對應的watcher,並且會用用這個屬性的key作為_computedWatchers 對象的key來保存這個watcher
當你默認傳一個function的時候,會把這個這個function當做watcher的getter,否則就會認為你傳了一個包含了get屬性的對象,並且是一個方法,
這個時候就會用這個get方法當做watcher的getter
3.使用$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, key: string, handler: any) { let options if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } vm.$watch(key, handler, options) } Vue.prototype.$watch = function ( expOrFn: string | Function, cb: Function, options?: Object ): Function { const vm: Component = this options = options || {} options.user = true const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { cb.call(vm, watcher.value) } return function unwatchFn () { watcher.teardown() } }
和使用$mount方法一樣,無論是你初始化的對象包含了watch屬性或者使用$watch 都會創建相對於的watcher
使用$watch會返回一個方法,當你對觀察的expOrFn不在感興趣的時候,可以用來刪除這個觀察者
二.dep
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() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
簡稱依賴,對vm中data返回的每個屬性都會初始化一個依賴,用來收集對這個屬性感興趣的watcher,也就是watcher中運行getter方法會用到的相關屬性,
那么這個屬性對應的依賴就會把watcher加入到dep下的subs隊列中去,也會把本身dep加入到watcher中的deps隊列中去,
當這個屬性的值改變的時候,通知watcher進行更新操作,也就是執行getter方法
屬性解釋:
subs:對某個屬性感興趣的watcher隊列
dep一般對應vm的中data方法返回的對象以及它的屬性
export default { data(){ return { name:"",
sex:1,
items:[]
} } }
比如上面的 data會有一個dep,name和sex也分別有一個dep
dep的創建過程
function initState (vm) { if (opts.data) { initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } } function initData (vm) { var data = vm.$options.data; data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}; if (!isPlainObject(data)) { data = {}; } var keys = Object.keys(data); var props = vm.$options.props; var i = keys.length; while (i--) { if (props && hasOwn(props, keys[i])) { ///warn } else if (!isReserved(keys[i])) { proxy(vm, "_data", keys[i]); } } // observe data observe(data, true /* asRootData */); } function observe (value, asRootData) { if (!isObject(value)) { return } var ob; if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__; } else if ( observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value); } return ob } var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, '__ob__', this); if (Array.isArray(value)) { var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); } }; Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive$$1(obj, keys[i], obj[keys[i]]); } }; function defineReactive$$1 ( obj, key, val, customSetter ) { 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; var childOb = 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(); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = observe(newVal); dep.notify(); } }); } Observer.prototype.observeArray = function observeArray (items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); } };
注:vm初始化會調用initState方法,所以省略了init的過程
整個過程簡而言之就是
1).initData
在初始化data的時候,是用一個三元運算符,判斷data是否是一個function,如果是的話,就運行 getData方法
function getData (data, vm) { try { return data.call(vm) } catch (e) { handleError(e, vm, "data()"); return {} } }
其實getData 也就是運行一次data方法而已,只是為了安全考慮,加了try catch
如果不是的話,就返回 data || {} 給它,這里有個小技巧就是,如果data不為空就返回data,否則就返回一個{},
然后 在對data里面的屬性設置代理,讓我們能夠用vm.key的方式來獲取或者設置data里面的數據,而不用vm._data.key的方式
在方法的最后面 就是調用observe 來生成一個observer(可以依賴的對象)
2).observe
首先判斷 是不是一個object,當然 這里的object 是一個泛型,是指 typeof 為 "object" 且 != null 的對象,也可以是Array,
function isObject (obj) { return obj !== null && typeof obj === 'object' }
如果不是一個object 那么就會中斷
再判斷 是否已經包含了一個 observer 對象,如果有 則返回這個observer對象
再判斷
observerState.shouldConvert: 是可以轉換的
!isServerRendering(): 不是服務器渲染
(Array.isArray(value) || isPlainObject(value)) 是 數組 或則 Object 類型
Object.isExtensible(value): 這個對象是可以添加屬性的, 做這個判斷是因為 會在Object上 新增一個__ob__屬性來對應它的Observer對象
!value._isVue: 不能是 vm對象, vm組件 嚴格來說 也是Object對象,但是他不能被觀察,所以有這個判斷
注: 判斷還挺多的。。。。
如果上述條件 任何一個不滿足 就會中斷,不過我們只需要關心的是。。我們傳進來的數據滿足是 數組 或者object對象就行了,其他的一般不會觸及
好了 下一步就是肉戲來了,就是創建一個 observer 對象
3). new observer
保存value數據對象,然后最重要的相關核心實現就從這里開始了,
首先創建一個dep依賴(說了這么多終於說到它了),這個dep 就對應的這個value數據對象,
然后 會判斷這個value是不是數組對象,
如果是的話, 先執行
augment(value, arrayMethods, arrayKeys)
而 augment 一般會對應這個方法
function protoAugment (target, src) { /* eslint-disable no-proto */ target.__proto__ = src; /* eslint-enable no-proto */ }
這一步比較關鍵,把這個數組對象默認的prototype對應的方法替換成arrayMethods 里面的方法,而arrayMethods 這個對象里面包含了哪些方法呢,請看代碼
var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto);[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] .forEach(function (method) { // cache original method var original = arrayProto[method]; def(arrayMethods, method, function mutator () { var arguments$1 = arguments; // avoid leaking arguments: // http://jsperf.com/closure-with-arguments var i = arguments.length; var args = new Array(i); while (i--) { args[i] = arguments$1[i]; } var result = original.apply(this, args); var ob = this.__ob__; var inserted; switch (method) { case 'push': inserted = args; break 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','pop','shift','unshift','splice','sort','reverse' 方法,並且arrayMethods的prototype是Array的prototype,
具體作用在下面回和例子結合說到
然后,再執行 observeArray,
Observer.prototype.observeArray = function observeArray (items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); } };
其實也就是對數據里面的每個元素再進行觀察, 從observe的判斷條件來看,普通的數據就不要想了,此元素 必須是一個object 或者array,才會生成一個observer對象(這點很重要)
好了,通過上面對數組的實現,可以解釋一下 我們平時 在用數組稍不注意就會犯的一個錯誤, 就是改變一個數組的普通元素的值,但是 界面上卻沒有響應它的改動呢,
比如
<template> <div id="app" class="app"> <p v-for="value of ary">{{value}}</p> </div> </template> <script> export default { data(){ return { ary:[1,2,3,4] } }, mounted(){ setTimeout(()=>{ this.ary[2] = 10; }, 2000); } }
上述代碼,會在兩秒之后改變界面的顯示嗎?
不會,因為 this.ary[2] = 10, 這個只是普通的操作,vue2 沒有對這個屬性添加相關的dep(我覺得是為了性能考慮吧,不然的話 數組里面的數據大了,對每個數據都建立一個dep,那就太恐怖了)
這樣的操作,是沒有任何改變的,
而上面vue2 特意自己實現了array 的一些相關方法去實現這個效果,所以如果要改變普通元素的數據的時候我們可以這樣,
mounted(){ setTimeout(()=>{ this.ary.splice(2,1,10); }, 2000); }
感興趣的可以測試下,
起作用的原因在於arrayMethods這個對象,當我們操作splice的時候,不是操作的ary的原生的splice方法,而是操作的arrayMethods里面的splice方法,
而這個方法在執行了原生的對應的方法后,還會去調用ob.dep.notify()方法執行更新
以上是如果這個數據類型是數組的時候,當不是的時候,那么執行walk方法,
然后獲取對象的keys 循環對key調用defineReactive方法,然后當這個數據 又是一個object或者array對象的時候,又再一次去觀察它
可以說defineReactive 這個方法里面的實現,是實現雙向綁定最最核心的一個方法了,其他的一系列都是在這基礎上做擴展,或是為了健壯性或者為了解耦等等,
這個方法實現了對data的每個屬性創建一個dep,並且對data的原生屬性的操作利用Object.defineProperty方法定義一個set和get的代理,讓data和dep產生關系,
而且 在set和get具體實現里,也實現了 讓dep和watcher 產生關系,所以說這個是vue2實現雙向綁定最最最核心的方法了,因為這么重要,說就單獨在下一個部分來解釋它
三 dep 和 watcher 是如何聯系起來的
function defineReactive$$1 ( obj, key, val, customSetter ) { 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; var childOb = 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(); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = observe(newVal); dep.notify(); } }); }
下面就來看看 這個方法的具體實現:
首先定義了一個dep,然后在獲取這個屬性的描述對象,用這個對象來判斷這個屬性是不是可以配置的,如果為否,則退出,因為使用Object.defineProperty
這個方法是需要這個屬性是可以配置的
然后獲取這個屬性的 get和set方法並緩存,因為Object.defineProperty會重定義這兩個方法,會覆蓋它們,緩存起來以便於調用
再然后 let childOb = observe(val) 這句話,就實現生成子對象的observer,這樣就實現了循環調用,對所有子對象都能夠生成observer以及子對象的屬性值的dep
當然,這個子對象得是 Object 或者 array
請注意:核心來了,使用Object.defineProperty方法 定義set 和get方法
我們看set方法實現:
首先得到value值,這里用三元運算符來判斷之前緩存的get方法是否存在,如果存在就調用這個get方法否則直接返回val值
然后再 判斷 Dep.target 是否存在,我們看看源碼里面的注釋對這個屬性的解釋
// the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. Dep.target = null
這個屬性表示當前正在計算的watcher, 並且在用一個時間內整個全局內只會同時存在一個
就是說:正在運行 get方法的watcher,而get是一個方法,而js是單線程,不會同一時間運行多個方法,那當運行這個get方法過程中又可能存在調用其他watcher的get方法的情況,
比如一個vm組件的render方法里面包含computed的屬性的時候,這又是如何處理的呢,請看代碼
const targetStack = [] export function pushTarget (_target: Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { Dep.target = targetStack.pop() }
首先定義了一個target棧,是通過一個數組實現的,
運行watcher的get方法的時候 會先調用pushTarget方法,方法里面會 判斷Dep.target,如果存在則把它壓進棧里面,然后把新的taget賦值給Dep.target
watcher的個體方法后面會運行popTarget,把target棧的最后一個元素賦值給Dep.target,當然如果不存在,那么Dep.target為空
通過代碼可以看出來,Dep永遠是對應當前正在計算的watcher,如果還不懂的話也沒關系,最后會通過一個例子把整個相關的流程都解釋一遍,
我們回到set方法實現:
判斷Dep.target是否存在,如果存在那么表示,我獲取這個屬性的值的是在運行watcher的get方法過程中(因為只有運行watcher的get方法,才會pushTarget,
而且在get方法后面還會運行popTarget把Dep.target重置),那么代表這個屬性值是和這個watcher是有關聯的,
好,既然是有關聯的,那么dep 就和watcher要進行關聯操作了,即執行dep.depend方法,這個方法實現了,把dep放進watcher的newDeps里面,為什么是newDeps
而不是deps呢,因為deps代表當前的依賴,而newDeps是表示新的依賴,即運行完這次更新之后,這個watcher的相關依賴,而運行get方法的時候會清楚當前的deps里面的依賴,
把newDeps里面的依賴復制到deps里面去
如果有childOb的話,也會把childOb同watcher關聯,因為如果有childOb,那么這個value肯定是一個對象,操作這個對象有兩個方式,要么獲取value對象的子屬性,要么設置
這個對象為新對象,這和childOb都有關系,所以需要把childOb同watcher關聯
再判斷value值是否為array,如果是的話,就去關聯array下面的對象
以上就是整個dep 和watcher 關聯的部分了
而set實現就是實現通知關聯的watcher進行更新,因為只有數據改變的時候,才會需要更新,所以把通知更新實現放在set方法里面
set具體實現:
當改變一個屬性對應的值的時候, 先獲取現在的值並做比較,如果相同就退出
然后不同的話,那么就進行復制操作
然后再執行更新操作,而在watcher部分說過,watcher的getter有3個類型:1:vm的render方法,2:computed的屬性,3:watch的屬性
這個時候,大多數情況是執行vm。render進行更新,當然這個render只是返回一個vNode(虛擬節點)然后調用vm的update進行真正的更新,
這里為了簡化理解,所以這樣說
上面就是整個關聯的部分,在總結一下過程就是:在創建watcher進行的時候,1和3方式的會立即去運行get方法,然后和相關運算用到的屬性對應的dep建立關聯,
而2對應的computed的屬性因為是懶更新的,所以在創建watcher的時候並不會立即執行關聯,而是在調用的時候才會用到,而一般是在vm.render方法里面會有調用
雙向綁定到這里就解釋完了,本來還想繼續在這里用一個例子來說明整個過程,但是發現貌似這篇文字已經很多了,那就放在下一篇《雙向綁定續》里面來解釋
注:因為是第一次寫博客,所以可能一些東西沒考慮完善,或者錯誤的地方,請指正,謝謝