解析都在代码注释中;
<!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'>
{{name}}
<input type="text" v-model='name'>
</div>
<script>
//存储所有的订阅者
class Dep{
constructor(){
//储存所有的订阅者
this.subs=[]
}
//添加订阅者
addSub(item){
this.subs.push(item);
}
//通知所有的订阅者
notify(){
for(let ele of this.subs){
ele.update();
}
}
}
//订阅类
class Watcher{
constructor(vm,node,name,type){
Dep.target=this;
this.vm = vm;//vue实例
this.node = node;//节点Dom对象
this.name=name;//该dom节点使用的vue实例上的data数据属性名
this.type=type;//当给不同节点赋值时,表单需要用到value属性,而文本节点则需要通过nodeValue赋值
this.update();
Dep.target=null;
}
update(){
//调用get方法获取数据的最新值
this.get();
//为节点赋值
this.node[this.type]=this.value;
}
//获取vm实例上数据的最新值
get(){
this.value=this.vm[this.name];
}
}
class Compile{
constructor(node,vm){
this.frag = this.nodeToFragment(node,vm);
}
//循环将所有节点
//这里我们只涉及一层子节点
nodeToFragment(node,vm){
let fragment = document.createDocumentFragment();
let child;
while(child=node.firstChild){
//将所有节点进行编译以及对双向绑定的表单元素绑定事件
this.compileElement(child,vm);
fragment.appendChild(child);
}
return fragment;
}
compileElement(node,vm){
//编译元素节点以及文本节点
//匹配文本节点的正则表达式
let reg = /\{\{(.*)\}\}/;
if(node.nodeType===1){
//元素节点
//获取节点的所有属性,检测是否有v-model,即是否需要进行双向数据绑定
let attributes = node.attributes;
for(let ele of attributes){
if(ele.nodeName==='v-model'){
let name = ele.nodeValue;
node.addEventListener('input',(e)=>{
vm[name]=e.target.value;
})
//订阅数据改变通知
new Watcher(vm,node,name,'value');
}
}
}
if(node.nodeType===3){
//文本节点
if(reg.test(node.nodeValue)){
let name = RegExp.$1;//获取第一个捕获组中的内容
new Watcher(vm,node,name,'nodeValue');
}
}
}
}
class Vue{
constructor(params){
//将传递过来的data内的数据挂载为实例的根元素
let data = params.data;
let keyArr = [];
for(let key in data){
this[key] = data[key];
keyArr.push(key);
}
this.observe(keyArr,this)
//编译模板文件
this.el=params.el;
let model = document.querySelector(this.el);
//接收编译好的模板代码
let compiledModel = new Compile(model,this).frag;
//将编译好的模板代码添加至DOM中
model.appendChild(compiledModel);
}
//循环递归遍历每一个属性
observe(keyArr,self){
keyArr.forEach(key=>{
//循环判断每一个属性值如果是对象需要递归进行遍历
if(Object.prototype.toString.call(self[key])==='[object Object]'){
let dataArr = [];
for(let keyOne in self[key]){
dataArr.push(keyOne);
}
observe(dataArr,self[key]);
}else{
this.defineActive(self,key,self[key]);
}
})
}
//数据劫持
defineActive(self,key,val){
//创建一个储存订阅者实例的Dep实例
let dep = new Dep();
Object.defineProperty(this,key,{
get(){
//检测,如果Dep.target不为null,则代表有watcher实例在初始化中,需要将其放入Dep实例中存储
if(Dep.target){
dep.addSub(Dep.target);
}
return val;
},
set(newVal){
if(val===newVal){
return;
}
val=newVal;
//当vue实例中的数据发生变化,通知所有的订阅者
dep.notify();
}
})
}
}
let vm = new Vue({
el:'#app',
data:{
name:'jack',
age:16
}
})
</script>
</body>
</html>