vue雙向綁定
原理:
vue.js 則是采用資料劫持結合釋出者-訂閱者模式的方式,通過
Object.defineProperty()
來劫持各個屬性的
setter
,
getter
,在資料變動話,通知訂閱者,觸發更新回調函數,重新渲染視圖。
原理圖:
observer
用來實作對每個vue中的
data
中定義的屬性循環用
Object.defineProperty()
實作資料劫持,以便利用其中的
setter
和
getter
,然後通知訂閱者,訂閱者會觸發它的
update
方法,對視圖進行更新。
在vue中
v-model
,
v-name
,
{{}}
等都可以對資料進行顯示,也就是說假如一個屬性都通過這三個指令了,那麼每當這個屬性改變的時候,相應的這個三個指令的html視圖也必須改變,于是vue中就是每當有這樣的可能用到雙向綁定的指令,就在一個Dep中增加一個訂閱者,其訂閱者隻是更新自己的指令對應的資料,也就是
v-model='name'
和
{{name}}
有兩個對應的訂閱者,各自管理自己的地方。每當屬性的
set
方法觸發,就循環更新 Dep中的 訂閱者。
代碼實作
1. observer實作
function defineReactive (obj, key, val) {
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function() {
//添加訂閱者watcher到主題對象Dep
if(Dep.target) {
// JS的浏覽器單線程特性,保證這個全局變量在同一時間内,隻會有同一個監聽器使用
dep.addSub(Dep.target);
}
return val;
},
set: function (newVal) {
if(newVal === val) return;
val = newVal;
console.log(val);
// 作為釋出者發出通知
dep.notify();//通知後dep會循環調用各自的update方法更新視圖
}
})
}
function observe(obj, vm) {
Object.keys(obj).forEach(function(key) {
defineReactive(vm, key, obj[key]);
})
}
2.實作compile:
function Compile(node, vm) {
if(node) {
this.$frag = this.nodeToFragment(node, vm);
return this.$frag;
}
}
Compile.prototype = {
nodeToFragment: function(node, vm) {
var self = this;
var frag = document.createDocumentFragment();
var child;
while(child = node.firstChild) {
console.log([child])
self.compileElement(child, vm);
frag.append(child); // 将所有子節點添加到fragment中
}
return frag;
},
compileElement: function(node, vm) {
var reg = /\{\{(.*)\}\}/;
//節點類型為元素(input元素這裡)
if(node.nodeType === 1) {
var attr = node.attributes;
// 解析屬性
for(var i = 0; i < attr.length; i++ ) {
if(attr[i].nodeName == 'v-model') {//周遊屬性節點找到v-model的屬性
var name = attr[i].nodeValue; // 擷取v-model綁定的屬性名
node.addEventListener('input', function(e) {
// 給相應的data屬性指派,進而觸發該屬性的set方法
vm[name]= e.target.value;
});
new Watcher(vm, node, name, 'value');//建立新的watcher,會觸發函數向對應屬性的dep數組中添加訂閱者,
}
};
}
//節點類型為text
if(node.nodeType === 3) {
if(reg.test(node.nodeValue)) {
var name = RegExp.$1; // 擷取比對到的字元串
name = name.trim();
new Watcher(vm, node, name, 'nodeValue');
}
}
}
}
3. watcher實作
function Watcher(vm, node, name, type) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.type = type;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function() {
this.get();
this.node[this.type] = this.value; // 訂閱者執行相應操作
},
// 擷取data的屬性值
get: function() {
console.log(1)
this.value = this.vm[this.name]; //觸發相應屬性的get
}
}
4 實作Dep來為每個屬性添加訂閱者
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
})
}
}
總結:
首先為每個vue屬性用
Object.defineProperty()
實作資料劫持,為每個屬性配置設定一個訂閱者集合的管理數組
dep
;然後在編譯的時候在該屬性的數組dep中添加訂閱者,
v-model
,
{{}}
,
v-bind
都會添加一個訂閱者,接着會為input添加監聽事件,修改值就會為該屬性指派,觸發該屬性的
set
方法,在set方法内通知訂閱者數組dep,訂閱者數組循環調用各訂閱者的
update
方法更新視圖。
詳細推薦閱讀:
https://www.cnblogs.com/zhouyideboke/p/9626804.html