参考文章:https://www.cnblogs.com/kidney/p/6052935.html
双向数据绑定要实现:view -- model -- model -- view
1.创建一个Vue对象,原型里要做的是接收数据,劫持并编译dom节点,将dom节点重新挂在app
2.接收数据的时候,为每一个数据都用defineProperty添加一个set,get方法
3.view -- model : 在劫持dom节点的时候,如果是有v-model的元素,把Vue中该变量的初始值赋值给该元素value,并添加监听addEventListener,输入数据的时候就赋值给Vue中的该变量
4.model -- view: 如果是{{}}节点,该节点值为Vue中该变量的值
5.让数据实时更新,就用到订阅者发布者模式,在4步骤编译获取数据的每个节点都定义一个订阅者,在2步骤defineProperty时为每个数据都添加一个唯一的发布者dep, 在获取数据get中将改数据的订阅者添加到发布者dep里,在set数据改变的时候调用发布者dep通知所有订阅者。
(订阅者发布者模式:为每一个数据添加一个单独的发布者,在get数据的时候添加一个订阅者,在set该数据的时候通过该数据的发布者通知所有该数据的订阅者。)
<!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;
//定义发布者
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 = /\{\{(.*)\}\}/;
//节点类型为元素
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');
}
}
}
//节点类型为text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; //获取匹配到的字符串
name = name.trim();
// node.nodeValue = vm[name]; //将值赋值给node节点
//在编译dom节点的时候添加订阅者
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;
}
//对data遍历
function observe(obj, vm) {
Object.keys(obj).forEach(function(key) {
defineReactive(vm, key, obj[key]);
});
}
//添加set get
function defineReactive(obj, key, value) {
//每个属性都添加一个发布者
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function() {
//获取数据的时候将订阅者添加到对应的发布者
if (appDep) dep.addSub(appDep);
return value;
},
set: function(newValue) {
if (newValue === value) return;
value = newValue;
// 发布者发布通知
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>
网友评论