<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>myVue</title>
</head>
<body>
<div id="myVue">
<h1>{{title}}</h1>
<input v-model="name" />
<p>{{name}}</p>
<button v-on:click="vueFun">click me</button>
<p>{{name}}</p>
<p>{{name}}</p>
</div>
<script>
//递归兼听每个数据属性
function defineReactive(data,key,val){
observe(val);
var sup = new Dep();
Object.defineProperty(data,key,{
enumerable: true,
configurable: true,
get:function(){
if(Dep.target){//是否需要添加订阅者
sup.addSub(Dep.target)
}
return val;
},
set:function(newVal){
if(val===newVal){
return ;
}
val = newVal;
sup.notify()
}
});
}
//兼听器
function observe(data){
if(!data || typeof data != "object"){
return ;
}
Object.keys(data).forEach(function(key){
defineReactive(data,key,data[key]);
});
}
//订阅器
function Dep(){
this.subs=[];
}
Dep.target=null;
Dep.prototype={
addSub:function(sub){
this.subs.push(sub);
},
notify:function(sub){
console.log(this.subs.length)
this.subs.forEach(function(sub){
sub.update();
})
}
}
//订阅者
function Watcher(vm,exp,cb){
this.vm=vm;
this.exp=exp;
this.cb=cb;
this.value= this.get();
}
Watcher.prototype={
update:function(){
this.run();
},
run:function(){
var value = this.vm.data[this.exp];
var oldVal = this.value;
if(value!==oldVal){
this.value=value;
this.cb.call(this.vm,value,oldVal);
}
},
get:function(){
Dep.target = this;
var value = this.vm.data[this.exp];
Dep.target=null;
return value;
}
}
function Compile(el,vm){
this.vm=vm;
this.el = document.querySelector(el);
this.fragment = null;
this.init();
}
Compile.prototype={
init:function(){
if(this.el){
this.fragment = this.nodeToFragment(this.el);
this.compileElement(this.fragment);
this.el.appendChild(this.fragment);
}else{
console.log("Dom not find");
}
},
nodeToFragment:function(el){
var fragment = document.createDocumentFragment();
var child = el.firstChild;
while(child){
fragment.appendChild(child);
child = el.firstChild;
}
return fragment;
},
compileElement:function(el){
var childNodes = el.childNodes;
var self = this;
if(!childNodes){
console.log("childNodes not find");
return false;
}
[].slice.call(childNodes).forEach(function(node){
var reg = /\{\{(.*)\}\}/;
var text = node.textContent;
if(self.isElementNode(node)){
self.compile(node);
}else if(self.isTextNode(node)&®.test(text)){
self.compileText(node,reg.exec(text)[1]);
}
if(node.childNodes&&node.childNodes.length){
self.compileElement(node);
}
})
},
compileText:function(node,exp){
var self = this;
var initText = this.vm[exp];
this.updateText(node,initText);
new Watcher(this.vm,exp,function(value){
self.updateText(node,value);
})
},
updateText:function(node,value){
node.textContent = typeof value =="undefined"?"":value;
},
isTextNode:function(node){
return node.nodeType==3;
},
compile:function(node){
var nodeAttrs = node.attributes;
var self = this;
Array.prototype.forEach.call(nodeAttrs,function(attr){
var attrName = attr.name;
if(self.isDirective(attrName)){
var exp = attr.value;
var dir = attrName.substring(2);
if(self.isEventDirective(dir)){//指令事件
self.compileEvent(node,self.vm,exp,dir);
}else{
self.compileModel(node,self.vm,exp,dir);
}
node.removeAttribute(attrName);
}
})
},
compileEvent:function(node,vm,exp,dir){
var eventType = dir.split(":")[1];
var cb = vm.methods && vm.methods[exp];
if(eventType && cb){
node.addEventListener(eventType,cb.bind(vm),false);
}
},
compileModel:function(node,vm,exp,dir){
var self = this;
var val = this.vm[exp];
this.modelUpdate(node,val);
new Watcher(this.vm,exp,function(value){
self.modelUpdate(node,value);
});
node.addEventListener("input",function(e){
var newValue = e.target.value;
if(val===newValue){
return;
}
self.vm[exp]=newValue;
val = newValue;
})
},
modelUpdate:function(node,value){
node.value = typeof value =="undefined"?"":value;
},
isDirective:function(attr){
return attr.indexOf("v-")===0;
},
isEventDirective:function(dir){
return dir.indexOf("on:")===0;
},
isElementNode:function(node){
return node.nodeType==1;
}
}
//把 observe 和 watcher 连接起来
function myVue(options){
var self = this;
this.vm = this;
this.data = options.data;
this.methods = options.methods;
Object.keys(this.data).forEach(function(key){
self.proxyKeys(key);
})
observe(this.data);
new Compile(options.el,this.vm);
options.mounted.call(this);
return this;
}
myVue.prototype={
proxyKeys:function(key){
var self = this;
Object.defineProperty(this,key,{
enumerable:false,
configurable:true,
get:function proxyGetter(){
return self.data[key]
},
set:function proxySetter(newVal){
self.data[key]=newVal;
}
});
}
}
var mvue = new myVue({
el:"#myVue",
data:{
title:"index",
name:"wellcome index"
},
methods:{
vueFun(){
this.name="this is ok"
console.log("success")
}
},
mounted:function(){
var self = this;
window.setTimeout(function(){
self.title="success";
},2000);
}
});
</script>
</body>
</html>