Open
Description
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>{{title}}</h2>
<h1>{{name}}</h1>
<input type="text" v-model="name" v-on:blur="blur">
<button v-on:click="clickMe">click me!</button>
</div>
<script>
// 监听器
function observe(data){
if(Object.prototype.toString.call(data)!=='[object Object]'){
return;
}
Object.keys(data).forEach(key=>{
defineReactive(data,key,data[key])
})
}
function defineReactive(data,key,value){
observe(value);
var dep = new Dep();
Object.defineProperty(data,key,{
enumerable: true,
configurable: true,
get: function(){
if(Dep.target){
dep.addSub(Dep.target); // 添加一个订阅者
}
return value;
},
set: function(newVal){
if(newVal === value){
return;
}
console.log('value changed,\nthe old value is '+ value + ',\nthe new value is ' + newVal);
value = newVal;
dep.notify(); // 如果数据变化,通知所有订阅者
}
})
}
// 消息订阅器
function Dep(){
this.subs = [];
}
Dep.prototype = {
constructor: Dep,
addSub: function(sub){
this.subs.push(sub);
},
notify: function(){
this.subs.map(sub=>{
sub.update();
})
}
}
// 订阅器
function Watcher(vm,exp,cb){
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get();
}
Watcher.prototype = {
constructor: Watcher,
get: function(){
Dep.target = this;
var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
Dep.target = null;
return value;
},
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);
}
}
}
// 解析器
/* 将需要解析的dom节点存入fragment片段 */
function Compile(el,vm){
this.vm = vm;
console.log(this.vm);
this.el = document.querySelector(el);
this.fragment = null;
this.init();
}
Compile.prototype = {
constructor: Compile,
init: function(){
if(!this.el){
return;
}
this.fragment = this.nodeToFragment(this.el);
this.compileElement(this.fragment);
this.el.appendChild(this.fragment);
},
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;
[].slice.call(childNodes).forEach(child=>{
var text = child.textContent;
var reg = /\{\{(.*)\}\}/;
if(self.isElementNode(child)){
self.compileDirective(child);
}
if(self.isTextNode(child) && reg.test(text)){
self.compileText(child, reg.exec(text)[1]);
}
if(child.childNodes && child.childNodes.length){
self.compileElement(child);
}
})
},
compileDirective: function(node){
var attrs = node.attributes;
var self = this;
[].slice.call(attrs).forEach(attr=>{
var name = attr.name;
if(self.isDirective(name)){
var exp = attr.value;
var dir = name.substring(2);
if(self.isEventDirentive(dir)){ // 事件指令
console.log('event')
self.compileEvent(node,self.vm,exp,dir)
}else{ // v-model指令
self.compileModel(node,self.vm,exp,dir)
}
node.removeAttribute(name);
}
})
},
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)
})
},
compileEvent: function(node,vm,exp,dir){
var eventName = dir.split(':')[1];
var cb = this.vm.methods && this.vm.methods[exp];
if(eventName && cb){
console.log('bind event')
node.addEventListener(eventName,cb.bind(vm),false);
}
},
compileModel: function(node,vm,exp,dir){
var self = this;
var val = this.vm[exp];
this.modelUpdater(node, val);
new Watcher(this.vm,exp,function(value){
self.modelUpdater(node,value);
})
node.addEventListener('input',function(e){
var newValue = e.target.value;
if(newValue===val){
return
}
self.vm[exp] = newValue;
val = newValue;
})
},
updateText: function(node,text){
node.textContent = typeof text === 'undefined' ? '' : text;
},
modelUpdater: function(node,text){
node.value = typeof text === 'undefined' ? '' : text;
},
isTextNode: function(node){
return node.nodeType === 3;
},
isElementNode: function(node){
return node.nodeType === 1;
},
isDirective: function(directive){
return directive.indexOf('v-')===0;
},
isEventDirentive: function(directive){
return directive.indexOf('on:')===0;
}
}
function SelfVue(options){
var self = this;
this.data = options.data;
this.methods = options.methods;
Object.keys(this.data).forEach(key=>{
self.proxyKey(key);
})
observe(this.data);
new Compile(options.el,this);
options.mounted.call(this);
}
SelfVue.prototype.proxyKey = function(key){
var self = this;
Object.defineProperty(self,key,{
get: function(){
return self.data[key];
},
set: function(newVal){
self.data[key] = newVal;
}
})
}
var selfVue = new SelfVue({
el: '#app',
data: {
title: 'hello world',
name: 'canfoo'
},
methods: {
clickMe: function () {
this.title = 'hello world';
}
},
mounted: function () {
window.setTimeout(() => {
this.title = '你好';
}, 1000);
}
})
</script>
</body>
</html>