实现的做法
- 发布订阅者模式
- 脏值检查
- 数据劫持
先从一个简单的开始
letmvvm={};
Object.defineProperty(mvvm,'hello', {
get:function() {
returndocument.getElementsByTagName("input")[0].value;
},
set:function(val) {
document.getElementsByTagName("input")[0].value=val;
document.getElementsByTagName('span')[0].innerHTML=val;
console.log("set被调用了,参数是:", val)
}
})
document.getElementsByTagName("input")[0].addEventListener('keyup',function(e) {
mvvm.hello=e.target.value;
})
这里利用了Object.defineProperty()里的getter setter方法,在修改mvvm对象上的hello属性时候,我们可以做一些事情,当触发set的时候,把input,span的值都修改成新的val,然后在input上监听keyup
event, 触发keyup,将mvvm.hello设置为当前事件对象的value,同时触发了set,改变了input,span的值
问题来了
在上例中,我们只是监听了input的keyup事件,而且只是绑定了mvvm.如果有很多obj呢,如果不只是input呢,如果不只是input的keyup呢,所以我们需要做一个
通用的模式
Vue的数据绑定实现
Two-way data-binding
{{ text }}
function observe(obj, vm) {
Object.keys(obj).forEach(function(key) {
defineReactive(vm, key, obj[key]);
});
}
function defineReactive(obj, key, val) {
var dep=newDep();
Object.defineProperty(obj, key, {
get:function() {
// 添加订阅者watcher到主题对象Dep
if(Dep.target) dep.addSub(Dep.target);
returnval
},
set:function(newVal) {
if(newVal===val)return
val=newVal;
// 作为发布者发出通知
dep.notify();
}
});
}
function nodeToFragment(node, vm) {
var flag=document.createDocumentFragment();
var child;
while(child=node.firstChild) {
compile(child,vm);
flag.append(child);// 将子节点劫持到文档片段中
}
return flag;
}
function compile(node, vm) {
var reg=/\{\{(.*)\}\}/;
// 节点类型为元素
if(node.nodeType===1) {
var attr = node.attributes;
// 解析属性
for(vari=0;i
if(attr[i].nodeName=='v-model') {
varname=attr[i].nodeValue;// 获取v-model绑定的属性名
node.addEventListener('input',function(e) {
// 给相应的data属性赋值,进而触发该属性的set方法
vm[name]=e.target.value;
});
node.value=vm[name];// 将data的值赋给该node
node.removeAttribute('v-model');
}
};
newWatcher(vm,node,name, 'input');
}
// 节点类型为text
if(node.nodeType===3) {
if(reg.test(node.nodeValue)) {
varname=RegExp.$1;// 获取匹配到的字符串
name=name.trim();
newWatcher(vm, node, name,'text');
}
}
}
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() {
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();
});
}
};
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'
}
});
网友评论