首先上源码,模拟vue的双向数据绑定原理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Two-way data-binding</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text">
{{ text }}
</div>
<script type="text/javascript">
// observer 主要是用来遍历对象中的data值,进行数据的劫持操作
function observe(obj, vm) {
Object.keys(obj).forEach(function(key) {
//调用劫持函数
defineReactive(vm, key, obj[key]);
});
}
// 数据劫持函数
function defineReactive(obj, key, val) {
// 声明一个Dep实例
var dep = new Dep();
//给对象定义新属性
Object.defineProperty(obj, key, {
get: function() {
// 添加订阅者watcher到主题对象Dep
// TODO Dep.target是什么意思
if(Dep.target) dep.addSub(Dep.target);
return val;
},
set: function(newVal) {
if(newVal === val) return;
val = newVal;
// 作为发布者发出通知
dep.notify();
}
});
}
//TODO
function nodeToFragment(node, vm) {
var flag = document.createDocumentFragment();
var child;
while(child = node.firstChild) {
compile(child, vm);
//将子节点劫持到文档片段中
flag.appendChild(child);
}
return flag;
}
//TODO
function compile(node, vm) {
// mustouch的正则
var reg = /\{\{(.*)\}\}/;
// 节点类型为元素
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;
node.addEventListener('input', function(e) {
// 给相应的data属性赋值,进而触发该属性的set方法
vm[name] = e.target.value;
});
// 将data的值赋给该node
node.value = vm[name];
node.removeAttribute('v-model');
}
};
// 声明watcher实例,监听node值的变化
new Watcher(vm, node, name, 'input');
}
// 节点类型为text
if(node.nodeType === 3) {
if(reg.test(node.nodeValue)) {
// 获取匹配到的字符串
var name = RegExp.$1;
name = name.trim();
// 将值放入监听中
new Watcher(vm, node, name, 'text');
}
}
}
// TODO
function Watcher(vm, node, name, nodeType) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.nodeType = nodeType;
this.update();
Dep.target = null;
}
Watcher.prototype = {
// 触发更新数据
update: function() {
this.get();
if(this.nodeType == 'text') {
this.node.nodeValue = this.value;
}
if(this.nodeType == 'input') {
this.node.value = this.value;
}
},
// 获取data中的属性值
get: function() {
// 触发相应属性的get
this.value = this.vm[this.name];
}
}
//TODO
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() { // 遍历订阅的值
this.subs.forEach(function(sub) {
sub.update();
});
}
};
// TODO
function Vue(option) {
this.data = option.data;
var data = this.data;
observe(data, this);
var id = option.el;
// 生成dom片段
var dom = nodeToFragment(document.getElementById(id), this);
console.log(dom);
// 编译完成后,将dom返回到app中
document.getElementById(id).appendChild(dom);
}
// DONE
var vm = new Vue({
el: 'app',
data: {
text: 'hello world!'
}
});
</script>
</body>
</html>