<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<input v-model="fanfan">{{fanfan}}
</div>
</body>
</html>
<script>
function mycompile(el,vm) {
//传入节点
let node=el
//进行子节点处理
this.frag=this.nodefragment(node,vm)
return this.frag
}
mycompile.prototype.nodefragment=function(node,vm){
let flag=document.createDocumentFragment()
let child
while (child=node.firstChild) {
//对每个子节点的input和{{}}进行识别
this.compileElement(child,vm)
//对于嵌套的进行递归
if(child.firstChild){
flag.append(this.nodefragment(child,vm))
}
flag.append(child)
}
return flag
}
mycompile.prototype.compileElement=function(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; // 获取v-model绑定的属性名
node.addEventListener('input', function(e) {
// 给相应的data属性赋值,进而触发该属性的set方法
vm[name]=e.target.value
console.log(e.target.value);
});
// node.value = vm[name]; // 将data的值赋给该node
new Watch(vm, node, name, 'value');
}
}
}
if(node.nodeType==3) //文本节点
{
if(reg.test(node.nodeValue)){
//key值
let name=RegExp.$1
//识别出来后进行data值的替换
node.nodeValue=vm[name]
//要进行双向绑定的联系 让vm里面值改变的时候也通知{{}}改变 通过watch进行收集 dep进行订阅 到时候改变就会dep进行发布
new Watch(vm,node,name,'nodeValue')
}
}
}
//对于vue对象 需要进行data的响应式绑定和dom树的模板解析
function myvue(options){
this.data=options.data
this.el=options.el
observer(this.data,this)
let dom= new mycompile(document.getElementById(this.el),this)
document.getElementById(this.el).appendChild(dom);
}
function observer(obj,vm){
Object.keys(obj).forEach((v)=>{
defineReactive(vm,v,obj[v])
})
}
function defineReactive(obj,key,value){
var dep = new Dep();
//如果是对象的话 还要进行递归响应式 并且将其挂载在对应的对象上
if( Object.prototype.toString.call(value)=='[object Object]'){
observer(value,value)
}
Object.defineProperty(obj,key,{
get: function() {
console.log('get');
//添加订阅者watcher到主题对象Dep
if(Dep.target) {
console.log('推入');
console.log(Dep.target);
// JS的浏览器单线程特性,保证这个全局变量在同一时间内,只会有同一个监听器使用
dep.depend(Dep.target);
}
//返回后作为vue实例上的属性
return value;
},
set:function(newvalue){
console.log('set');
if(newvalue === value) return;
value = newvalue;
// 作为发布者发出通知
dep.notify();
}
})
}
function Watch(vm,node,name,type){
Dep.target=this
this.vm=vm
this.node=node
this.name=name
this.type=type
//触发更新
this.update()
Dep.target=null
}
Watch.prototype.update=function(){
//触发vm的get函数 把这个watch推入观察者 !!!
this.node[this.type]=this.vm[this.name]
}
function Dep(){
this.subscribes=[]
}
Dep.prototype.depend=function(dep){
this.subscribes.push(dep)
}
Dep.prototype.notify=function(dep){
this.subscribes.forEach((sub)=>{
sub.update()
})
}
let vm=new myvue({
el:'app',
data:{
fanfan:'挂载fan',
xixi:'我',
myobj:{
one:'1',
two:'2'
},
},
})
</script>
通过监听DOM里面的input的键入操作,如果有v-model的属性就会改变vm对象中对应的属性值。触发set操作。
然后通知Dep去更改视图。