Vue雙向綁定原理
大部分都知道Vue是采用的是對象的get 和set方法來實現數據的雙向綁定的過程,本章將討論他是怎么利用他實現的。
vue雙向綁定其實是采用的觀察者模式,get和set方法只是實現觀察者模式的切入點,即在我們set的時候向觀察者發布消息,執行觀察者的操作,get的時候是為實現set能夠通知watcher進行相關處理做准備。下面我們來看一下數據初始化的流程;
數據初始化流程:
數據在初始化時,會調用方法 defineReactive 為數據綁定dep對象(以備之后使用),在進行掛載時會實例化一個進行頁面更新的watcher($watcher).,該watcher調用渲染函數(上圖的第5步),會調用模板里涉及到的屬性的get方法,即為每個屬性的dep對象加載對應的依賴$watcher,同時在$watcher下備份涉及到的dep對象。數據初始化時主要是調用data下的屬性的get方法,在數據更新時才會調用屬性的set方法,詳細可看下面的代碼注釋。
/** * 采用觀察者模式進行數據更新的監聽,subject為data下的所有屬性以及子孫屬性等 * * subject與watcher是多對多的關系 * 一個subject,如屬性data.A,可能對於處理頁面更新的Watcher,也可能對應$watch函數傳入的表達式更新的Watcher對象,或者觀察計算屬性的Watcher對象等 * 一個更新頁面的Watcher會對象data下的所有屬性以及子孫屬性等 * * * */ function defineReactive( 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; if (!getter && arguments.length === 2) { val = obj[key]; } var setter = property && property.set; // 如果 val為對象,創建一個觀察者Observe實例ob,先創建一個dep實例,賦值給Observe.dep ,保存實例只val.__ob__屬性, 類似val.__ob__ = ob; var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; //如果屬性之前就定義getter方法,則執行getter方法,並返回屬性值 if (Dep.target) { // Dep.target為執行更新的watcher對象 dep.depend(); //為該屬性添加觀察者(訂閱者),掛在對應的dep對象的subs:[]屬性下,對應的watcher也會存對應的目標關系與watcher.deps與watcher.depIds if (childOb) { //如果是對象,為對象添加該Watcher實例,可用於set, 數組改變,計算屬性等操作 childOb.dep.depend(); if (Array.isArray(value)) { //如果value是數據 采用遞歸的方式為為value下的對象成員添加Observe實例屬性 __ob__ 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 ("development" !== 'production' && customSetter) {//如果是開發環境,者打印一些警告 customSetter(); } if (setter) { //如果屬性之前就定義了setter方法,則執行setter方法 setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); // 數據有改變,通知觀察者進行操作 } }); }
數據更新流程:
這里將講解數據的更新流程,如果我重設了數據如data.A = 5;它將出發A屬性的set方法,該方法會對比當前設置的值與之前的值是否相等,如果不相等,在出發dep.notify()函數,類似觸發更新事件,代用更新操作,該方法會去調用dep對象下所有依賴watcher的update方法,該方法會排除重復的watcher,最終采用微任務或者定時的方式去執行watcher對應的run方法(run方法調用了watcher的get方法),之后將會調用渲染函數,如此又將與初始化的過程過程的第五步一致,調用data下的get方法,同時備份watcher至涉及到的dep對象下,將上次執行備份中涉及到的dep而本次執行沒涉及到dep下的依賴當前watcher刪除,想見 Watcher.prototype.cleanupDeps函數注釋
在初始化以及數據更新工程,都將調用watcher.get方法,為什么執行該方法就能保證以上功能正常執行呢
watcher.get方法會去調用watcher實例的getter,如果是進行頁面更新與渲染的watcher,getter方法這是去執行render函數並將render函數生成的vnode進行渲染。再執行render函數時會涉及到調用模板里的data屬性。從而觸發屬性的get方法。那又是采用什么方式保證屬性對應的dep是觸發對應的watcher?見Watcher.prototype.get注釋
Watcher.prototype.get = function get() { pushTarget(this); //該函數的作用是,將Dep.target的值推入堆棧中,並將當前的Watcher實例賦值給Dep.target var value; var 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
//如果是深度監聽watching,進行遞歸加載監聽觀察者
if (this.deep) { traverse(value); } popTarget(); //將堆棧中棧頂的值彈出堆棧並賦值給 Dep.target this.cleanupDeps();//與上一次的watcher對象依賴下的dep數據對比,清除沒有使用的dep對象 } return value };
在以上代碼,在初始化的時候執行watcher.get,其中會調用pushTarget(this),將當前的值賦給Dep.target ,即相當於賦給了一個全局變量。之后將執行watcher的this.getter方法,即render函數,在執行這個函數期間Dep.target一直保持為該watcher,以此保證屬性下的dep對象都是對應的watcher,如果在執行render期間有其他watcher執行打斷當前的執行,也會在執行其他watcher之后恢復該值,執行完this.getter執行popTarget();將從堆棧中取出執行上下文的watcher值並賦給Dep.target。
下面的代碼這是用於保證watcher對象與dep對象的正確依賴關系。同事備份執行watcher依賴的dep對象。
由於dep對象用於通知watcher執行相關的操作,所以他們之間會有一個多對多的對應關系。即dep需要通知哪些watcher,watcher又注冊到哪些dep對象中,下面的就是保證每次執行之后這個依賴關系都是正確的
/** * 備份本次更新的deps對象和depIds,同時將上次Watcher更新依賴的dep實例,但本次Watcher更新更新不依賴的dep實例下依賴Watcher備份數據刪除該Watcher實例 */ Watcher.prototype.cleanupDeps = function cleanupDeps() { var this$1 = this; var i = this.deps.length; while (i--) { // 獲取上一次觸發Wactcher更新的dep對象--dep var dep = this$1.deps[i]; // 如果該對象dep在新的watcher依賴下沒有則清除dep對象下依賴的該Watcher對象 if (!this$1.newDepIds.has(dep.id)) { dep.removeSub(this$1); } } var tmp = this.depIds; //將上次更新的depIds緩存 this.depIds = this.newDepIds; //備份本次更新的Watcher對應的deps對象的id this.newDepIds = tmp; //將上次更新的depId賦值給newDepIds,並在下一行進行清空 this.newDepIds.clear(); //將newDepIds清空,以備下次更新使用 tmp = this.deps; //deps 對象的備份與 depIds邏輯一致 this.deps = this.newDeps; //備份本次更新的Watcher對應的deps對象 this.newDeps = tmp; this.newDeps.length = 0; //將newDeps清空,以備下次更新使用 };