參考文章:https://www.cnblogs.com/kidney/p/6052935.html
雙向數(shù)據(jù)綁定要實(shí)現(xiàn):view -- model -- model -- view
1.創(chuàng)建一個(gè)Vue對(duì)象,原型里要做的是接收數(shù)據(jù),劫持并編譯dom節(jié)點(diǎn)瞻讽,將dom節(jié)點(diǎn)重新掛在app
2.接收數(shù)據(jù)的時(shí)候窜锯,為每一個(gè)數(shù)據(jù)都用defineProperty添加一個(gè)set,get方法
3.view -- model : 在劫持dom節(jié)點(diǎn)的時(shí)候管宵,如果是有v-model的元素截珍,把Vue中該變量的初始值賦值給該元素value,并添加監(jiān)聽addEventListener,輸入數(shù)據(jù)的時(shí)候就賦值給Vue中的該變量
4.model -- view: 如果是{{}}節(jié)點(diǎn)箩朴,該節(jié)點(diǎn)值為Vue中該變量的值
5.讓數(shù)據(jù)實(shí)時(shí)更新笛臣,就用到訂閱者發(fā)布者模式,在4步驟編譯獲取數(shù)據(jù)的每個(gè)節(jié)點(diǎn)都定義一個(gè)訂閱者隧饼,在2步驟defineProperty時(shí)為每個(gè)數(shù)據(jù)都添加一個(gè)唯一的發(fā)布者dep, 在獲取數(shù)據(jù)get中將改數(shù)據(jù)的訂閱者添加到發(fā)布者dep里沈堡,在set數(shù)據(jù)改變的時(shí)候調(diào)用發(fā)布者dep通知所有訂閱者。
(訂閱者發(fā)布者模式:為每一個(gè)數(shù)據(jù)添加一個(gè)單獨(dú)的發(fā)布者燕雁,在get數(shù)據(jù)的時(shí)候添加一個(gè)訂閱者诞丽,在set該數(shù)據(jù)的時(shí)候通過(guò)該數(shù)據(jù)的發(fā)布者通知所有該數(shù)據(jù)的訂閱者。)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>test vue</title>
</head>
<body>
<div id="app">
<input type="text" id="input-text" v-model="text" />
<br/> {{ text }}
<br/> {{ text }}
</div>
<script>
var appDep;
//定義發(fā)布者
function Dep() {
this.subs = [];
}
Dep.prototype = {
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
},
addSub: function(sub) {
this.subs.push(sub);
}
}
//訂閱者
function Watcher(vm, node, name) {
appDep = this;
this.name = name;
this.node = node;
this.vm = vm;
this.update();
appDep = null;
}
Watcher.prototype = {
update: function() {
this.get();
this.node.nodeValue = this.value;
},
get: function() {
this.value = this.vm[this.name];
}
}
//比較返回dom類型
function compile(node, vm) {
var reg = /\{\{(.*)\}\}/;
//節(jié)點(diǎn)類型為元素
if (node.nodeType === 1) {
var attr = node.attributes;
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue;
node.addEventListener('input', function(e) {
vm[name] = e.target.value;
});
node.value = vm[name];
node.removeAttribute('v-model');
}
}
}
//節(jié)點(diǎn)類型為text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; //獲取匹配到的字符串
name = name.trim();
// node.nodeValue = vm[name]; //將值賦值給node節(jié)點(diǎn)
//在編譯dom節(jié)點(diǎn)的時(shí)候添加訂閱者
new Watcher(vm, node, name);
}
}
}
//劫持dom
function nodeFragment(node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
compile(child, vm);
flag.appendChild(child);
}
return flag;
}
//對(duì)data遍歷
function observe(obj, vm) {
Object.keys(obj).forEach(function(key) {
defineReactive(vm, key, obj[key]);
});
}
//添加set get
function defineReactive(obj, key, value) {
//每個(gè)屬性都添加一個(gè)發(fā)布者
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function() {
//獲取數(shù)據(jù)的時(shí)候?qū)⒂嗛喺咛砑拥綄?duì)應(yīng)的發(fā)布者
if (appDep) dep.addSub(appDep);
return value;
},
set: function(newValue) {
if (newValue === value) return;
value = newValue;
// 發(fā)布者發(fā)布通知
dep.notify();
}
});
}
//定義Vue類
function Vue(option) {
this.data = option.data;
var data = this.data;
observe(data, this);
var id = option.el;
var dom = nodeFragment(document.getElementById(id), this);
//編譯完成將dom返回到app中
document.getElementById(id).appendChild(dom);
}
var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
}
});
</script>
</body>
</html>