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節點中
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中綁定的回調
實例化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進行視圖的更新
到此咱們的雙向數據綁定就結束了。。。