vue双向数据绑定主要是通过 Object对象的defineProperty属性,重写data的set和get函数来实现的,这里只会实现最基本的内容,主要实现v-model,v-bind 和v-click三个命令,其他命令也可以自行补充
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>myVue双向数据绑定</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="app">
<form>
<input type="text" v-model="number" />
<button type="button" v-click="increment">增加</button>
</form>
<h3 v-bind="number"></h3>
</div>
<script>
window.onload=function(){
// 挂载并创建根实例
var app=new myVue({
el: '#app',
data: {
number: 0,
},
methods: {
increment: function(){
this.number++;
}
}
});
}
// 定义myVue构造函数
function myVue(options){
// 自定义一个_init函数,为了初始化这个构造函数
this._init(options);
}
// 定义_init初始化函数
myVue.prototype._init=function (options){
this.$options=options;
this.$el=document.querySelector(options.el);
this.$data=options.data;
this.$methods=options.methods;
// 保存着view与model的映射关系,
this._binding={};
// 调用_observe函数对data进行处理
this._observe(this.$data);
// 用来解析指令(v-model,v-click,v-bind等指令),在过程中并对view和model进行绑定
this._compile(this.$el);
}
// 实现_observe函数
myVue.prototype._observe=function(obj){
var value;
for(var key in obj){
if(obj.hasOwnProperty(key)){
// 按照此例子,此处应该为 _binding:{ number: _directives:[] }
this._binding[key]={
_directives: [] //data里的对象对应的映射的指令集(v-model,v-click,c-bind等等)经过Watch类处理过后的结果集
}
value=obj[key]; //获取对象的属性值
if(typeof value === 'object'){ //如果对象值还是一个对象,则进行遍历处理
this._observe(value);
}
var binding=this._binding[key]; //定义变量binding来缓存遍历到的对象对应的指令集
Object.defineProperty(obj,key,{
enumerable: true,
configurable: true,
get: function(){
console.log("获取value: "+value);
return value;
},
set: function(newVal){
console.log("更新newVal: "+newVal,"之前的值: "+value);
if(newVal !== value){
value=newVal;
binding._directives.forEach(function(item){ //此例子就是当number改变时,触发update函数,更新binding[number]._directives中绑定的Watch类
item.update(); //调用update函数更新
});
}
}
});
}
}
}
// 实现_compile函数
myVue.prototype._compile=function(root){
var _this=this;
var nodes=root.children;
for(var i=0;i<nodes.length;i++){
var node=nodes[i];
if(node.children.length){ //对所有元素进行遍历操作
this._compile(node);
}
if(node.hasAttribute('v-click')){ // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++
node.onclick=(function(){
var attrVal=nodes[i].getAttribute('v-click');
return _this.$methods[attrVal].bind(_this.$data); //bind是使data的作用域与method函数的作用域保持一致
})()
}
if(node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName =='TEXTAREA')){
node.addEventListener('input',(function(key){
//_this._binding['number']._directives = [一个Watch实例]
// 其中Watch.prototype.update = function () {
// node['vaule'] = _this.$data['number']; 这就将node的值保持与number一致
// }
var attrVal=node.getAttribute('v-model');
_this._binding[attrVal]._directives.push(new watch(
'input',
node,
_this,
attrVal,
'value'
))
return function(){
_this.$data[attrVal]=nodes[key].value; //使number的值与node的value保持一致,已经实现了数据双向绑定
}
})(i));
}
if(node.hasAttribute('v-bind')){ //如果有v-bind,只需要及时将v-bind中的值更新为data里的值即可
var attrVal=node.getAttribute('v-bind');
_this._binding[attrVal]._directives.push(new watch(
'text',
node,
_this,
attrVal,
'innerHTML'
));
}
}
}
// 定义一个watch类
function watch(name,el,vm,exp,attr){
this.name=name; //指令名称,文本节点则对应text,
this.el=el; //指令对应的dom元素
this.vm=vm; //所属myVue实例
this.exp=exp; //指令对应的值,如“number”
this.attr=attr; //指令对应的属性值,如“innerHTML”
this.update(); //同步更新操作
}
watch.prototype.update=function(){
this.el[this.attr]=this.vm.$data[this.exp]; //比如h3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新
}
</script>
</body>
</html>