vue.js 則是採用數據劫持結合發佈者-訂閱者模式的方式,經過Object.defineProperty()來劫持各個屬性的setter,getter,在數據變更時發佈消息給訂閱者,觸發相應的監聽回調。咱們先來看Object.defineProperty()這個方法。javascript
var obj = {};
Object.defineProperty(obj, 'name', {
get: function() {
console.log('我被獲取了')
return val;
},
set: function (newVal) {
console.log('我被設置了')
}
})
obj.name = 'yzg';/ /在給obj設置name屬性的時候,觸發了set這個方法
var val = obj.name; //在獲得obj的name屬性,會觸發get方法
複製代碼
經過Object.defineProperty()能夠實現數據劫持,是的屬性在賦值的時候觸發set方法html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="demo"></div>
<input type="text" id="inp">
<script> var obj = {}; var demo = document.querySelector('#demo') var inp = document.querySelector('#inp') Object.defineProperty(obj, 'name', { get: function() { return val; }, set: function (newVal) {//當該屬性被賦值的時候觸發 inp.value = newVal; demo.innerHTML = newVal; } }) inp.addEventListener('input', function(e) { // 給obj的name屬性賦值,進而觸發該屬性的set方法 obj.name = e.target.value; }); obj.name = 'fei';//在給obj設置name屬性的時候,觸發了set這個方法 </script>
</body>
</html>
複製代碼
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]);
})
}
複製代碼
compile的目的就是解析各類指令稱真正的html。vue
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');
}
}
}
}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');
}
}
}
}
複製代碼
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
}
}
複製代碼
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
})
}
}
複製代碼
這樣一來整個數據的雙向綁定就完成了。java
首先咱們爲每一個vue屬性用Object.defineProperty()實現數據劫持,爲每一個屬性分配一個訂閱者集合的管理數組dep;而後在編譯的時候在該屬性的數組dep中添加訂閱者,v-model會添加一個訂閱者,{{}}也會,v-bind也會,只要用到該屬性的指令理論上都會,接着爲input會添加監聽事件,修改值就會爲該屬性賦值,觸發該屬性的set方法,在set方法內通知訂閱者數組dep,訂閱者數組循環調用各訂閱者的update方法更新視圖。node