Vue之九數據劫持實現MVVM的數據雙向綁定

vue是經過數據劫持的方式來作數據綁定的,其中最核心的方法即是經過Object.defineProperty()來實現對屬性的劫持,達到監聽數據變更的目的。javascript

若是不熟悉defineProperty,猛戳這裏html

 整理了一下,要實現mvvm的雙向綁定,就必需要實現如下幾點:
一、實現一個數據監聽器Observer,可以對數據對象的全部屬性進行監聽,若有變更可拿到最新值並通知訂閱者
二、實現一個指令解析器Compile,對每一個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數
三、實現一個Watcher,做爲鏈接Observer和Compile的橋樑,可以訂閱並收到每一個屬性變更的通知,執行指令綁定的相應回調函數,從而更新視圖
四、mvvm入口函數,整合以上三者vue

一、實現Observer

咱們知道能夠利用Obeject.defineProperty()來監聽屬性變更
那麼將須要observe的數據對象進行遞歸遍歷,包括子屬性對象的屬性,都加上 settergetter
這樣的話,給這個對象的某個值賦值,就會觸發setter,那麼就能監聽到了數據變化。。相關代碼能夠是這樣:java

var data = {name: 'kindeng'};
observe(data);
data.name = 'dmq'; // 監聽到值變化了 kindeng --> dmq

function observe(data) {
    if (!data || typeof data !== 'object') {
        return;
    }
    // 取出全部屬性遍歷
    Object.keys(data).forEach(function(key) {
        defineReactive(data, key, data[key]);
    });
};

function defineReactive(data, key, val) {
    observe(val); // 監聽子屬性
    Object.defineProperty(data, key, {
        enumerable: true, // 可枚舉
        configurable: false, // 不能再define
        get: function() {
            return val;
        },
        set: function(newValue) {
            console.log('哈哈哈,監聽到值變化了 ', val, ' --> ', newValue);
            val = newValue;
        }
    });
}

  上面的思路整理中咱們已經明確訂閱者應該是Watcher, 並且var dep = new Dep();是在 defineReactive方法內部定義的,因此想經過dep添加訂閱者dom

完整方法	
function Observer(data) { this.data = data; this.walk(data); } Observer.prototype = { walk: function(data) { var me = this; Object.keys(data).forEach(function(key) { me.convert(key, data[key]); }); }, convert: function(key, val) { this.defineReactive(this.data, key, val); }, defineReactive: function(data, key, val) { var dep = new Dep(); var childObj = observe(val); Object.defineProperty(data, key, { enumerable: true, // 可枚舉 configurable: false, // 不能再define get: function() { if(Dep.target) { dep.depend(); } return val; }, set: function(newVal) { if(newVal === val) { return; } val = newVal; // 新的值是object的話,進行監聽 childObj = observe(newVal); // 通知訂閱者 dep.notify(); } }); } }; function observe(value, vm) { if(!value || typeof value !== 'object') { return; } return new Observer(value); }; var uid = 0; function Dep() { this.id = uid++; this.subs = []; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); console.log(this.subs) }, depend: function() { Dep.target.addDep(this); }, removeSub: function(sub) { var index = this.subs.indexOf(sub); if(index != -1) { this.subs.splice(index, 1); } }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); } }; Dep.target = null;

  這裏已經實現了一個Observer了,已經具有了監聽數據和數據變化通知訂閱者的功能,那麼接下來就是實現Compile了mvvm

 

二、實現Compile

compile主要作的事情是解析模板指令,將模板中的變量替換成數據,而後初始化渲染頁面視圖,並將每一個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變更,收到通知,更新視圖,如圖所示:函數

 

 由於遍歷解析的過程有屢次操做dom節點,爲提升性能和效率,會先將跟節點el轉換成文檔碎片fragment進行解析編譯操做,解析完成,再將fragment添加回原來的真實dom節點中性能

三、實現Watcher

Watcher訂閱者做爲Observer和Compile之間通訊的橋樑,主要作的事情是:
一、在自身實例化時往屬性訂閱器(dep)裏面添加本身
二、自身必須有一個update()方法
三、待屬性變更dep.notice()通知時,能調用自身的update()方法,並觸發Compile中綁定的回調,則功成身退。ui

實例化Watcher的時候,調用get()方法,經過Dep.target = watcherInstance標記訂閱者是當前watcher實例,強行觸發屬性定義的getter方法,getter方法執行的時候,就會在屬性的訂閱器dep添加當前watcher實例,從而在屬性值有變化的時候,watcherInstance就能收到更新通this

四、實現MVVM

MVVM做爲數據綁定的入口,整合Observer、Compile和Watcher三者,經過Observer來監聽本身的model數據變化,經過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通訊橋樑,達到數據變化 -> 視圖更新;視圖交互變化(input) -> 數據model變動的雙向綁定效果。

知。

function MVVM(options) {
    this.$options = options;
    var data = this._data = this.$options.data, me = this;
    // 屬性代理,實現 vm.xxx -> vm._data.xxx
    Object.keys(data).forEach(function(key) {
        me._proxy(key);
    });
    observe(data, this);
    this.$compile = new Compile(options.el || document.body, this)
}

MVVM.prototype = {
    _proxy: function(key) {
        var me = this;
        Object.defineProperty(me, key, {
            configurable: false,
            enumerable: true,
            get: function proxyGetter() {
                return me._data[key];
            },
            set: function proxySetter(newVal) {
                me._data[key] = newVal;
            }
        });
    }
};

  這裏主要仍是利用了Object.defineProperty()這個方法來劫持了vm實例對象的屬性的讀寫權,使讀寫vm實例的屬性轉成讀寫了vm._data的屬性值

相關文章
相關標籤/搜索