雙向數據綁定,我們首先來看數據改變如何觸發頁面的刷新。
首先通過Object.defineProperty( )對vue的data設置getter和setter。實現攔截數據。我們的數據都是在html里以指令或者變量的形式存在。
一個笨辦法
遍歷全部DOM,把需要更新數據的DOM都和對應的數據映射起來,存在在一個對象中,就是一個數據一個對象。然后全部放在一個數組中。每當有數據變化,就去遍歷這個數組,把對應數據的DOM進行更新。這么做的缺點是每次都需要遍歷全部的數組。
別的辦法
一一對應的關系,是不是該用對象?想想node里路由傳參數的那個問題。我們一個對象例如為BInging:{},key是data的每個值,value是依賴這個數據的DOM。然后數據變化BInging[變化的數據]就可以取出value更新相關的DOM。
vue的方法
vue中并沒有專門設置這樣一個對象。而是把抵賴放入了data每個值得getter函數中。更新放進了setter中。就是吧獨立出來的BInging對象和data對象進行了融合。
因為這里的get和set會因為閉包造成作用域不被回收,所以dep會保存在每個data的get和set作用域中。
v-model更新數據
給v-model加上input監聽事件。觸發事件的時候去更新data的值。視圖自然就跟著變了。
實現雙向綁定MVVM
vue的雙向綁定原理及實現
vue雙向綁定數據原理
我們的目錄結構為
compile.js // 解析vue的DOM
dep.js // 增加監聽數據和Watcher中間的橋梁
index.js // 初始化操作
observer.js // 對data數據進行劫持
watcher // 數據變化需要通知的訂閱者
首先我們給vue的data的所以數據用Object.defineProperty
其次我們來解析DOM。
??采用遞歸方法從vue傳入的el(一般為el: '#app'
,)根元素進行向下遍歷。
目前我們先只考慮文本節點。只是把{{}}等解析成頁面展示的內容放入頁面內比較簡單。
??現在如果data的數據變化,頁面的內容也需要變化。這樣改怎樣做?
一個數據例如data
下的name
變化肯能頁面有多處用到這個變量。所以要把所有用到name
變量的地方都進行更新。我們每個data
有一個dep
對象。用來存放所有的依賴。這就需要我們。變量DOM
的時候,碰到name
就把相應的dom
更新函數存放到name
的dep
中。在什么位置進行dep
的存放呢?第一次變量DOM
肯定會觸發變量的get
。我們放到get函數中。
vue中,比較不好理解的就是Dep.target。為什么需要Dep.target
var uid = 0;
function Dep() {
this.id = uid++;
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
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; //
只有獲取數據就會觸發get
,但不能通過手動的alert(dat.name)
這種代碼。也給dep
增加依賴吧。這樣也沒有依賴可以加啊。所以Dep.target
就是為了標志訂閱者Watcher
的。就是只有我們new Watcher()
的時候Dep.target
值才為真,才會被添加到dep
中。Dep.target
可以在watcher.js
和observer.js
中通過 import Dep from './dep.js'
來或者或者設置Dep.target
這個變量。為什么不放在Dep構造函數的原型中呢?因為Dep.target 還需要通過watcher.js
進行設置。這個Dep.target
到底是什么有什么用處呢?
import Dep from './dep.js'
function Watcher () {
this.get()
}
get: function() {
Dep.target = this;
var value = this.getter.call(this.vm, this.vm); // 解析DOM中的表達式觸發data的get函數。
Dep.target = null;
return value;
},
當觸發get函數時候設置Dep.target
,經過判斷可以觸發dep.depend();
或者dep.addSub(Dep.target)
目的都是為了給dep中增加依賴的watcher。
Dep.prototype = {
depend: function() {
Dep.target.addDep(this); // Dep.target就是剛剛設置的Watcher實例
},
addSub:function() {
this.subs.push(sub)
},
}
dep.depend();
又轉而觸發了Watcher
的addDep
。為什么呢?
因為我們需要在dep
中添加watcher
實例。dep
實例或者構造函數中并沒有watcher
實例啊。所以只能把通過傳參數,把一方傳到另一方去進行二合一。這里把dep
實例傳入到了watcher
實例中。
addDep: function(dep) {
dep.addSub(this);
this.depIds[dep.id] = dep;
},
addDep
的this
為watcher
實例dep
為Dep.target.addDep(this)
的this
也就是dep
實例。
然而發現直接
dep.addSub(Dep.target)
就可以實現把watcher
添加到dep
中。因為Dep.target
就是watcher
實例。現在我們的
data
的dep
添加watcher
完成,只需要在data
的set
函數觸發時候。遍歷dep
中的subs
進行觸發就可以達到更新頁面的效果
對應關系