上一遍文章介紹了Vue模版渲染的實現(https://segmentfault.com/a/11...),這篇文章將繼續介紹雙向綁定的實現vue
官網demo以下,當data。message的值變化,input的value值也會相應的變化;當用戶改變input框中的內容時data.message的值也會跟着改變node
<div id="app"></div> new Vue({ el: '#app', template: `<div> <input v-model="message" placeholder="edit me"> <p>Message is: {{ message }}</p> </div>`, data(){ return { message: 'jixiangwu', } } })
當數據變化時,視圖會直接更新,在本例中當data.message改變時,dom中綁定了data.message的視圖都會更新
上一篇文章中介紹過,new Vue的過程當中會將template字符串轉換成render函數,render函數執行後會獲得vnode對象(虛擬dom),在調用_update方法會將虛擬dom更新爲真實的瀏覽器dom,代碼以下:react
updateComponent = function () { //vm._render()生成vnode對象,vm._update()更新dom vm._update(vm._render(), hydrating); }; //對vue實例新建一個Watcher監聽對象,每當vm.data數據有變化,Watcher監聽到後負責調用updateComponent進行dom更新 vm._watcher = new Watcher(vm, updateComponent, noop);
updateComponent方法在Watcher初始化時會調用一次,後續的調用就涉及到MVVM的機制了,讓咱們從頭開始分析
Vue初始化時會對data中的全部屬性進行observe,調用defineReactive方法,將data屬性轉化爲getter/setters存取方式。本文demo中的data={message:「jixiangwu」}至關於以下的調用:defineReactive(vm.data,'message',vm.data['message'])segmentfault
//vue對象的生命週期中會調用initData方法 function initData (vm) { var data = vm.$options.data; observe(data, true /* asRootData */); } function observe (value, asRootData) { ob = new Observer(value); } //對data進行監聽 var Observer = function Observer (value) { if (Array.isArray(value)) { this.observeArray(value); } else { this.walk(value); } } //對data中的全部屬性調用defineReactive,將其轉化爲getter/setters存取方式 //Walk through each property and convert them into getter/setters. Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]); } }; function defineReactive(obj,key,val){ //利用閉包爲每一個屬性綁定一個dep對象(可視爲發佈者,負責發佈屬性是否有變化) const dep = new Dep(); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { //每次new一個watcher(訂閱者)對象的時候須要計算依賴的dep對象,Dep.target就是當前正在計算依賴的watcher對象 if (Dep.target) { //調用屬性的getter方法時,存在Dep.target則將當前dep和watcher綁定 dep.depend(); } }, set: function reactiveSetter (newVal) { //調用屬性的setter方法時,dep同時發佈一次屬性變化的通知到全部依賴的watcher對象 dep.notify(); } } }
defineReactive用到了Object.defineProperty 方法,這也是vue不支持ie8的緣由,這個方法的主要做用就是set和get函數,同時也能夠看到vue針對data中的全部屬性都會new一個dep對象,dep對象裏面會存放全部依賴此屬性的watcher對象,此處用到了發佈/訂閱模式,dep和watcher分別是發佈者和訂閱者,每當data中的屬性變化dep對象就會通知全部依賴的watcher去更新dom,下面詳細分析一下這個過程
上一篇提到,因爲template中引用了{{ message }}屬性,所以render函數裏面會調用到vm.meessage,這時就會觸發defineReactive設置的get方法,get方法裏面就會進行(該屬性)依賴的收集,那麼get方法裏的Dep.target是啥呢?
上一篇提到dom初次渲染是經過(監聽整個模版的)watcher對象初始化時調用watcher.get方法實現的,watcher.get方法主要是計算getter函數的值(本例中是updateComponent,更新dom)和計算依賴(哪些屬性的dep對象),Dep.target就是當前接受計算(依賴)的全局唯一的watcher對象,具體方法以下:
一、pushTarget(this),將this(當前watcher對象)賦值給Dep.target
二、調用this.getter,this.getter會訪問全部依賴的屬性,同時觸發屬性的getter方法
三、調用屬性getter方法中的dep.depend(),完成dep和wathcher的綁定
四、popTarget()將Dep.target值設爲targetStack棧中的上一個(沒有則爲空)瀏覽器
// 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; var targetStack = []; //Evaluate the getter, and re-collect dependencies. Watcher.prototype.get = function get () { //將this賦值給Dep.target pushTarget(this); //執行wacther的更新操做,本文中是執行updateComponent方法 this.getter.call(vm); popTarget(); } function pushTarget (_target) { if (Dep.target) { targetStack.push(Dep.target); } Dep.target = _target; } function popTarget () { Dep.target = targetStack.pop(); }
繼續看defineReactive中dep.depend方法幹了啥,其實就是dep對象上維護了一個watcher對象的隊列,wathcer對象上也維護了一份dep的隊列緩存
Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); } }; Watcher.prototype.addDep = function addDep (dep) { var id = dep.id; //將dep對象加入到wather對象的newDeps隊列中 this.newDepIds.add(id); this.newDeps.push(dep); if (!this.depIds.has(id)) { // 同時將watcher對象也加入到dep對象的subs隊列中 dep.addSub(this); } }; Dep.prototype.addSub = function addSub (sub) { this.subs.push(sub); };
data值變化時會觸發setter方法中的dep.notify,通知綁定在dep對象上的全部watcher對象調用update方法更新視圖(watcher.update最終調用了updateComponent,用到了緩存隊列,不必定當即觸發)閉包
Dep.prototype.notify = function notify () { // stabilize the subscriber list first var subs = this.subs.slice(); for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } };
總結
一、對data進行observe,針對data屬性調用Object.defineProperty設置getter和setter,同時綁定一個dep對象
二、new Watcher(vm, updateComponent, noop)監聽整個dom的變化
三、watcher初始化時調用updateComponent,updateComponent調用render函數更新dom(此時還會將該watcher對象賦值給全局對象Dep.target,進行依賴收集)
四、在watcher對象依賴收集期間,render函數訪問data中的屬性(如本例的data.message),觸發data.message的getter方法,在getter方法中會將data.message綁定的dep對象和wathcer對象創建對應關係(互相加入到對方維護的隊列屬性上)
五、後續data屬性的值變化時dep對象會通知全部依賴此data屬性的watcher對象調用updateComponent方法更新視圖app
視圖變化 -> 數據更新主要是經過v-model實現的,v-model本質上不過是語法糖,它負責監聽用戶的輸入事件以更新數據,本例中dom
<input v-model="message">
基本等同於下面的效果函數
<input :value="message" @input="message = $event.target.value"/>