vue-雙向數據綁定原理分析二--Mr.Ember

vue-雙向數據綁定原理分析二--Mr.Emberhtml

摘要:雙向數據綁定是vue的核心,經過是數據劫持結合發佈者-訂閱者模式的模式實現的。vue

上一講提到了怎麼簡單的模仿雙向數據綁定,下面咱們將會分析下vue雙向數據綁定源碼的簡單實現。node

實現原理數組

雙向數據綁定是經過數據的劫持結合發佈者-訂閱者模式的方式實現的。瀏覽器

數據劫持、vue是經過Object.definedProperty()實現數據劫持,其中會有getter()和setter()方法。當讀取屬性值時,就會觸發getter()方法,在view中若是數據發生了變化,就會經過Object.defineProperty()對屬性設置一個setter函數,當數據改變了就會觸發這個函數;app

 

一. 實現一個監聽器observer函數

observer主要是經過Object.defineProperty()來監聽屬性的變更,那麼將須要observer的數據對象進行遞歸遍歷,包括子屬性對象,都加上setter和getter,給這個對象的某個值賦值,
就會觸發setter,從而監聽數據的變化。

 

Observer.prototype = {
    walk: function(data) {    //執行函數
        var self = this;
        // 經過對一個對象進行遍歷,對這個對象全部的屬性進行監聽
        Object.keys(data).forEach(function(key) {
            self.defineReactive(data, key, data[key]);
        });
    },
    defineReactive: function(data, key, val) {
        var dep = new Dep();
        // 遍歷全部的子屬性
        var childObj = observe(val);
        Object.defineProperty(data, key, {
            get: function getter() {
                if(Dep.target) {
                    // 添加一個訂閱者
                    console.log(Dep.target)
                    dep.addSub(Dep.target);
                }
                return val;
            },
            // 若是一個對象屬性值發生改變,就會出發setter中的dep.notify(),通知watcher訂閱者數據發生變動,執行對應訂閱者的更新函數,來更新視圖
            set: function setter(newVal) {
                if(newVal === val) {
                    return;
                }
                val = newVal;
                // 新的值是object的話,進行監聽
                childObj = observe(newVal);
                dep.notify();
            }
        })
    }
}

監聽數據變化以後就是怎麼通知訂閱者了,因此須要實現一個消息訂閱器,維護數組,用來收集訂閱者,數據變更觸發notify,再調用訂閱者的update方法性能

// 建立Observer實例
function observe(value, vm) {
    if(!value || typeof value !== 'object') {
        return;
    }
    return new Observer(value);
}

// 消息訂閱器Dep()主要負責收集訂閱者,而後在屬性變化的時候執行對應訂閱者的更新函數
function Dep() {
    this.subs = [];
}
Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub);
    },
    // 通知訂閱者變動
    notify: function() {
        this.subs.forEach(function(sub) {
            sub.update();
        })
    }
};
Dep.target = null;

 

二. 實現一個Compilethis

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

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

 
 
function Compile(el, vm) {
  this.vm = vm;
  this.el = document.querySelector(el);
  this.fragment = null;
  this.init();
}

Compile.prototype = {
  init: function() {
    if(this.el) {
      this.fragment = this.nodeToFragment(this.el); //將綁定的DOM元素添加到fragment元素
      this.compileElement(this.fragment); //解析模板
      this.el.appendChild(this.fragment); //再把模板添加到元素上
    } else {
      console.log('DOM元素不存在')
    }
  },
  nodeToFragment: (el) => { //把要解析的元素單獨拿出來解析 把數據一下拿出來解析操做減小DOM操做 減小瀏覽器的重繪和重排
    var fragment = document.createDocumentFragment();
    var child = el.firstChild;
    while(child) {
      //將DOM元素移入fragment
      fragment.appendChild(child);
      child = el.firstChild;
    }
    return fragment;
  },
}
 

compileElement方法將遍歷全部的子節點,進行掃描編譯解析,調用對應的指令渲染函數進行數據渲染,並調用對應的指令更新函數進行綁定。

Compile.prototype = {
    //.....
    compileElement: function(el) {       //解析模版數據  判斷是不是元素節點仍是文本節點  有子節點的再次循環遍歷
        var childNodes = el.childNodes;
        var self = this;
        [].slice.call(childNodes).forEach(function(node) {
            var reg = /\{\{(.*)\}\}/;     //用來 標記一個子表達式的開始和結束位置
            var text = node.textContent;
            // 若是是元素節點
            if(self.isElementNode(node)) {
                self.compile(node);
                // 若是是文本節點
            } else if(self.isTextNode(node) && reg.test(text)) {
                console.log(node);

                self.compileText(node, reg.exec(text)[1]);
                console.log(reg.exec(text)[0]);
            }
            // 若是元素有子節點,再回調compileElement函數
            if(node.childNodes && node.childNodes.length) {
                self.compileElement(node);
            }
        });

    },
    compile: function(node) {    //解析元素指令  事件指令 綁定指令
        var nodeAttrs = node.attributes;  //元素屬性
        var self = this;
        Array.prototype.forEach.call(nodeAttrs, function(attr) {
            var attrName = attr.name;
            if(self.isDirective(attrName)) {
                var exp = attr.value;  
                var dir = attrName.substring(2);
                if(self.isEventDirective(dir)) {   //事件指令
                    self.compileEvent(node, self.vm, exp, dir);
                } else {      //v-model指令
                    self.compileModel(node, self.vm, exp, dir);
                }
                node.removeAttribute(attrName)
            }
        })
    },
    compileText: function(node, exp) {    // 解析文本
        var self = this;
        var initText = this.vm[exp];
        this.updateText(node, initText);
        new Watcher(this.vm, exp, function(value) {    //給文本添加一個監聽器
            self.updateText(node, value);
        })
    },
    compileEvent: function(node, vm, exp, dir) {    //解析事件
        var eventType = dir.split(':')[1];
        var cb = vm.methods && vm.methods[exp];

        if(eventType && cb) {  //增長一個監聽事件
            node.addEventListener(eventType, cb.bind(vm), false);
        }
    },
    compileModel: function(node, vm, exp) {    //解析model
        var self = this;
        var val = this.vm[exp];
        this.modelUpdater(node, val);
        new Watcher(this.vm, exp, function(value) {     //給model添加一個監聽器
            self.modelUpdater(node, value);
        })
        node.addEventListener('input', function(e) {
            var newValue = e.target.value;
            if(val === newValue) {
                return;
            } 
            self.vm[exp] = newValue;
            val = newValue;
        });
    },
    updateText: function(node, value) {   //
        node.textContent = typeof value == 'undefined' ? '' : value;
    },
    modelUpdater: function(node, value) {
        node.value = typeof value == 'undefined' ? '' : value;
    },
    isDirective: function(attr) {
        return attr.indexOf('v-') == 0;
    },
    isEventDirective: function(dir) {
        return dir.indexOf('on:') == 0;
    },
    isElementNode: function (node) {    
        return node.nodeType == 1;
    },
    isTextNode: function (node) {
        return node.nodeType === 3;
    }




}

經過遞歸遍歷保證了每一個節點和子節點會編譯解析到,包括{{}}表達式裏的文本節點。指令的聲明規定,是經過特定前綴的節點屬性來標記。監聽數據和綁定更新函數是在compileUtil.bind()方法中,經過new Watcher()添加回調來接收數據變化的通知。

接下來實現一個watcher

 

三.實現一個Watcher

Watcher訂閱者做爲Observer和Compile之間通訊的橋樑,主要作的是:

1. 在自身實例化時往屬性訂閱器(dep)中添加本身

2. 自身必須有一個update()方法

3. 待屬性變更dep.notice()通知時,能調用自身的uodate()方法,並觸發Compile中綁定的回調

 
 
function Watcher(vm, exp, cb) {
  this.cb = cb;
  this.vm = vm;
  this.exp = exp;
  this.value = this.get(); //將本身添加到訂閱器的操做
}

Watcher.prototype = {
  update: function() {
    this.run();
  },
  run: function() {
    var value = this.vm.data[this.exp];
    var oldVal = this.value;
    if(value !== oldVal) {
      this.value = value;
      this.cb.call(this.vm, value, oldVal);
    }
  },
  get: function() {
    Dep.target = this;
    var value = this.vm.data[this.exp]; //這裏會觸發屬性的getter,從而添加訂閱者
    Dep.target = null;
    return value;
  }
}
 

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

 

四. 實現一個MVVM

function SelfVue(options) {
    var self = this;
    this.data = options.data;
    this.methods = options.methods;
    

    Object.keys(this.data).forEach(function(key) {   //定義數據監聽時object能夠操做,不可枚舉
        self.proxyKeys(key);
    })

    observe(this.data);      //監聽數據
    new Compile(options.el, this);   //解析模板
    options.mounted.call(this);    //全部事情處理好後執行mounted
    console.log(this.__proto__); 


}

SelfVue.prototype = {
    proxyKeys: function(key) {
        var self = this;
        Object.defineProperty(this, key, {
            enumerable: false,
            configurable: true,
            get: function getter() {
                return self.data[key];  // 數據讀取
            },
            set: function setter (newVal) {
                self.data[key] = newVal;
            }
        })
    }
}

MVVM是負責安排observer,watcher,compile的工做

1. observer實現對MVVM自身model數據劫持,監聽數據的屬性變動,並在變更時進行notify

2. compile實現指令解析,初始化視圖,並訂閱數據變化,綁定好更新函數

3. watcher一方面接收observer經過Dep傳遞過來的數據變化,一方面通知compile進行視圖的更新

到此咱們的雙向數據綁定就結束了。。。

相關文章
相關標籤/搜索