如何實時監聽一個對象----模仿vue寫一個對象的雙向數據綁定

最近學習vue源碼,在學習關於數據雙向綁定的時候。看了好幾遍,仍是有不少點不太理解。部門的大神建議本身按照思想模仿的寫一個,體會會深不少。因而照作了,還真是神清氣爽。這篇文章記錄本身在寫demo時碰見的思路和問題,往後回憶複習起來也方便。vue

vue的雙向數據綁定是區分普通對象和數組的。數組的比較複雜,下篇再介紹。今天介紹vue對於對象數據的雙向綁定。vue是經過數據劫持的方式來實現雙向數據綁定的。數據劫持的核心就是object.defineProperty().簡單介紹下這個方法。這個方法是es5定義的,通過該方法定義的對象屬性會變成訪問器屬性。如下是一個簡單的例子:數組

 
 
function Observer(obj,key,value){
    if(Object.prototype.toString.call(value)=='[object Object]'){ Object.keys(value).forEach(function(key){ arguments.callee(value,key,value[key]); }) } Object.defineProperty(obj,key,{ enumerable:true, configurable:true, get:function(){ }, set:function(){ } }) }

訪問器屬性的最大特色即是內部能夠指定get、set方法。在對屬性進行值訪問的時候會調用定義的get方法,對屬性進行賦值的時候會調用set方法。數據結構

接着說雙向數據綁定。雙向數據綁定分爲如下三個部分:函數

Observer:負責數據劫持,把全部的屬性轉換成訪問器屬性,達到對數據進行觀測的目的。須要對數據進行遞歸觀測,由於數據的屬性值還有多是對象學習

Watcher:數據的觀察者,在數據發生變化以後執行的相應的回調函數,須要對數據進行遞歸watch,由於數據的屬性值還有多是對象this

Dep(Dependency):顧名思義,是Observer和Watcher的鏈接。如何鏈接呢?每個observer會建立一個Dep實例,實例在get數據的時候爲數據收集watcher,在set的時候執行watcher內的回調方法。es5

以上是vue中的作法。我本身實現demo的時候就是根據這個思路進行實現的。spa

先是Observer,遞歸將屬性設置爲訪問器屬性,代碼以下:prototype

function Observer(obj,key,value){
    if(Object.prototype.toString.call(value)=='[object Object]'){ Object.keys(value).forEach(function(key){ new arguments.callee(value,key,value[key]); }) } Object.defineProperty(obj,key,{ enumerable:true, configurable:true, get:function(){ return value; }, set:function(newVal){ if(value===newVal)return;
        value = newVal; } }) }

先判斷屬性值value是否是對象,若是是,還須要對對象進行遞歸調用,觀測數據雙向綁定

接着是Watcher,目的在於在數據發生變化的時候執行相應的回調函數

function Watcher(data,k,v,fn){
    if(Object.prototype.toString.call(data)==='[object Object]'){
        Object.keys(v).forEach(function(key){
            new arguments.callee(v,key,v[key],fn);
        })
    }
    this.fn = fn;
    data[k];
}

也是先判斷是否是對象,是的話遞歸調用觀察屬性值。如何讓watcher和observer產生聯繫呢?

observer中對全部的屬性設置成了訪問器屬性,因此若是咱們在watcher中調用屬性,求屬性的值就會調用到屬性的get方法。

既然observer和watcher能夠在get方法內產生鏈接,那麼是否是能夠在get的時候收集不一樣的watcher,而後在set函數唄調用的時候執行這些watcher中的方法。這樣就須要在observer中引入一個對象,在get函數內收集watcher,在set函數內遍歷執行watcher的回調方法,已達到動態響應的目的。

這個對象就是Dep。每一個observer內都會實例化一個Dep對象,用於收集watcher和用於執行watcher,根據這個思路,能夠獲得如下的代碼:

function Dep(){
    var sub=[];
    this.addSub=function(watcher){
        this.sub.push(watcher);
    };
    this.notify=function(){
        this.sub.forEach(function(watcher){
            watcher.fn();
        })
    }
}

根據思路,Dep當中須要一個存儲watcher的數據結構,從添加和遍歷的角度選擇,數組比較合適。而後是須要一個添加watcher的方法,在就是須要一個遍歷watcher的方法。進而,得出了以上的代碼。

 如今三個組件都已經有了,那他們之間怎麼協調工做呢?按照以前的思路,和咱們如今有的代碼。在observer中加入Dep收集和執行依賴的代碼。就發現,存在怎麼在get中獲得watcher的實例的問題。既然Dep自己做爲watcher和observer的鏈接橋樑,那這個事情就讓Dep作吧。此時須要作的事情是,須要一個變量,在Watcher中收集watcher實例,在get中將Watcher實例放入Dep實例的數組中,以便於set中使用。

思考這個變量的功能,他不能出如今構造函數和原型鏈中,這樣watcher的變化會實時的提如今每一個實例上。那麼只有在構造函數自己這個函數對象定義這個變量比較合適了。函數對象上的變量不會經過new操做符影響到全部實例,又能完成存儲watcher的功能。

先在watcher中收集,那麼watcher中的代碼以下:

function Watcher(data,k,v,fn){
    if(Object.prototype.toString.call(data)==='[object Object]'){
        Object.keys(v).forEach(function(key){
            new arguments.callee(v,key,v[key],fn);
        })
    }
    this.fn = fn;
    Dep.target = this;//
    data[k];
    Dep.target=null;//
}

在watcher調用屬性求值以前,將watcher保存到Dep.target變量中,在求值以後(get中Dep實例收集了以後)將該值置爲null,這樣就不會在別的屬性求值的時候影響到別的屬性。

已經在Watcher中收集到了watcher實例,那麼observer中如何使用呢。看以下代碼:

function Observer(obj,key,value){
    var dep = new Dep();
    if(Object.prototype.toString.call(value)=='[object Object]'){
        Object.keys(value).forEach(function(key){
           new arguments.callee(value,key,value[key]);
        })
    }
    Object.defineProperty(obj,key,{
        enumerable:true,
        configurable:true,
        get:function(){
            if(Dep.target){//存儲依賴
                dep.addSub(Dep.target);//
            }//
            return value;
        },
        set:function(newVal){
            if(value===newVal)return;
         value = newVal;
            dep.notify();//執行依賴
        }
    })
}            

到這一步,咱們基本上對象的雙向綁定已經完成了。全部的功能都已經實現了。

在我本身運行調試的時候發現一個問題。若是set的值又是一個對象,那麼對象的屬性改變將沒法獲得監控。因此,在set中加上以上代碼就完整了:

if(Object.prototype.toString.call(value) ==='[object Object]'){
    Object.keys(value).forEach(function(key){
         new Observer(value,key,value[key]);
         new Watcher(value,key,value[key],function(v,key){
            console.log('你修改了數據');
            // document.getElementById('dd').innerHTML=v.key;
      });
   })
}

以上就是我根據vue實例實現的關於對象的實時監聽的小demo了。

最後,我想了想。爲何vue要分這三個部分作呢?細想起來watcher主要的功能也就是set的時候的回調函數。若是沒學習過vue的源碼,應該就是直接把函數傳入observer作回調函數就是了。這樣watcher省了,dep也不用了。

可是又一想,同一個屬性可能同時有幾個watcher,就會要執行多個回調函數。那麼就有了watcher和dep的必要了。

相關文章
相關標籤/搜索