1. 程式人生 > >Vue之九數據劫持實現MVVM的數據雙向綁定

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

ldo img 枚舉 需要 == eject def xxx prot

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

如果不熟悉defineProperty,猛戳這裏

整理了一下,要實現mvvm的雙向綁定,就必須要實現以下幾點:
1、實現一個數據監聽器Observer,能夠對數據對象的所有屬性進行監聽,如有變動可拿到最新值並通知訂閱者
2、實現一個指令解析器Compile,對每個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數
3、實現一個Watcher,作為連接Observer和Compile的橋梁,能夠訂閱並收到每個屬性變動的通知,執行指令綁定的相應回調函數,從而更新視圖
4、mvvm入口函數,整合以上三者

技術分享圖片

1、實現Observer

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

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添加訂閱者

完整方法	
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了

2、實現Compile

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

技術分享圖片

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

3、實現Watcher

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

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

4、實現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的屬性值

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