<!DOCTYPE html>
<html>
<head>
<title>Vue.js双向绑定的实现原理</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text">
{{ text }}
</div>
<script type="text/javascript">
//订阅者
function Dep(){
this.subs = [];
}
Dep.prototype = {
addSub:function(sub){
this.subs.push(sub);
},
notify:function(){
this.subs.forEach(function(sub){
sub.update();
});
}
}
//发布者
function Watcher (vm, node, name) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function () {
this.get();
this.node.nodeValue = this.value;
},
// 获取data中的属性值
get: function () {
this.value = this.vm[this.name]; // 触发相应属性的get
}
}
//对象访问器
function defineReactive(obj,key,val) {
var dep = new Dep();
Object.defineProperty(obj,key,{
get:function(){
// 添加订阅者watcher到主题对象Dep
if(Dep.target)dep.addSub(Dep.target);
console.log(dep.subs)
return val;
},
set:function(newVal){
if(newVal === val) return;
val=newVal;
//作为发布者发布通知
dep.notify();
console.log(val);
}
})
}
//观察者,观察data对象的变更,使用Object.defineProperty添加订阅者和发布通知
function observe(obj,vm){
Object.keys(obj).forEach(function(key){
defineReactive(vm,key,obj[key]);
});
}
//编译方法
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();
new Watcher(vm,node,name);
}
}
}
//使用DocumentFragment劫持挂载的目标节点
function nodeToFragment(node,vm){
var flag = document.createDocumentFragment();
var child;
while(child = node.firstChild){
compile(child,vm);
flag.append(child);
}
return flag;
}
function Vue(options){
this.data = options.data;
var data = this.data;
observe(data,this);
var id = options.el;
var dom = nodeToFragment(document.getElementById(id),this);
//编译完成后将dom返回到app中
document.getElementById(id).appendChild(dom);
}
var vm = new Vue({
el:'app',
data:{
text:'hello world'
}
})
</script>
</body>
</html>