贴上代码便于日后复习
文件如下:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="message.a">
<div>
<ul>
<li></li>
</ul>
</div>
{{message.a}}
</div>
<script src="./watcher.js"></script>
<script src="./observer.js"></script>
<script src="./compile.js"></script>
<script src="./MVVM.js"></script>
<script>
let vm=new MVVM({
el:"#app",
data:{
message:{
a:'222222'
}
}
})
</script>
</body>
</html>
MVVM.js:
class MVVM{
constructor(options){
//先把可用的东西挂载到实例上
this.$el=options.el;
this.$data=options.data;
//如果有要编译的模板(el)我就开始编译
if(this.$el){
//数据劫持。把对想的所有属性改成
//get和set方法
new Observer(this.$data);
//用数据和元素进行编译
new Compile(this.$el,this);
}
}
}
observer.js:
class Observer{
constructor(data){
this.observe(data)
}
observe(data){
//要对这个data数据将原有的属性改成getset的形式
if(!data||typeof data !== 'object'){
return
}
//要将数据一一劫持先获取到data的key和value
Object.keys(data).forEach(key=>{
//劫持
this.defineReactive(data,key,data[key])
this.observe(data[key])//深度递归劫持
})
}
//定义响应式
defineReactive(obj,key,value){
let that=this
let dep=new Dep()
Object.defineProperty(obj,key,{
enumerable:true,//可枚举
configurable:true,
get(){
Dep.target&&dep.addSub(Dep.target)
return value
},
set(newValue){//当给data属性设置值得时候,更改获取的属性的值
if(newValue!=value){
that.observe(newValue)//如果是对象继续劫持
value=newValue
dep.notify()
}
}
})
}
}
class Dep{
constructor(){
//订阅的数组
this.subs=[]
}
addSub(watcher){
this.subs.push(watcher)
}
notify(){
this.subs.forEach(watcher=>watcher.update())
}
}
compile.js:
class Compile{
constructor(el,vm){
this.el=this.isElementNode(el)?el:document.querySelector(el);//#app document.
this.vm=vm;
if(this.el){
//如果这个元素能取到,我们才开始编译
//1.先把这些真实DOM移入到内存中fragment
let fragment=this.node2fragment(this.el)
//2.编译=> 提取想要的元素节点 v-model 和文本节点 {{}}
this.compile(fragment)
//把编译好的fragment再塞回到页面里去
this.el.appendChild(fragment)
}
}
//专门写一些辅助的方法:如判断他是不是指令
//判断el是不是dom节点的方法
isElementNode(node){
return node.nodeType===1;
}
//判断是不是指令:v-
isDirective(name){
return name.includes('v-')
}
//专门放一些核心的方法:
compileElement(node){
//带v-model
let attrs=node.attributes;//取出当前节点的属性
Array.from(attrs).forEach(attr=>{
//判断属性名是不是包含v-model
let attrName=attr.name
if(this.isDirective(attrName)){
//取到对应的值放到节点中
let expr=attr.value;
// let type=attrName.slice(2)//方法一
let [,type]=attrName.split('-')//方法二
//node this.vm.$data expr
//toto......
CompileUtil[type](node,this.vm,expr)
}
})
}
compileText(node){
//带{{sdsd}}
let expr=node.textContent;//取文本中的内容
let reg=/\{\{([^}]+)\}\}/g
// 首先 reg=/\{\{\}\}/g 所有大括号要转义 /g为全局
// 接着 reg=/\{\{([^}]+)\}\}/g
if(reg.test(expr)){
//node this.vm.$data text
//todo......
CompileUtil['text'](node,this.vm,expr)
}
}
compile(fragment){
//需要递归
let childNodes=fragment.childNodes
Array.from(childNodes).forEach(node=>{
if(this.isElementNode(node)){
//isElementNode方法在上面定义过
//是元素节点,则需要继续深入检查
//这里需要编译元素
this.compileElement(node);
this.compile(node)
}else{
//这里需要编译文本
this.compileText(node)
}
})
}
node2fragment(el){//需要将el中的内容全放到内存中
//文档碎片
let fragment=document.createDocumentFragment();
let firstChild;
while(firstChild = el.firstChild){
fragment.appendChild(firstChild)
}
return fragment
}
}
CompileUtil={//获取实例上对应的数据
getVal(vm,expr){
expr=expr.split('.')//[a,v,c,s,a,w,r]
return expr.reduce((prev,next)=>{//vm.$date.a
return prev[next]
},vm.$data)
},
getTextVal(vm,expr){//获取编译文本后的结果
return expr.replace(/\{\{([^}]+)\}\}/g,(...arguments)=>{
return this.getVal(vm,arguments[1])
})
},
text(node,vm,expr){//文本处理
let updateFn=this.updater['textUpdater']
let value=this.getTextVal(vm,expr)
expr.replace(/\{\{([^}]+)\}\}/g,(...arguments)=>{
new Watcher(vm,arguments[1],(newValue)=>{
updateFn&&updateFn(node,this.getTextVal(vm,expr))
})
})
updateFn&&updateFn(node,value)
},
setVal(vm,expr,value){
expr=expr.split('.');
return expr.reduce((prev,next,currentIndex)=>{
if(currentIndex === expr.length-1){
return prev[next]=value;
}
return prev[next];
},vm.$data)
},
model(node,vm,expr){//输入框处理
let updateFn=this.updater['modelUpdater']
new Watcher(vm,expr,(newValue)=>{
updateFn&&updateFn(node,this.getVal(vm,expr))
})
node.addEventListener('input',(e)=>{
let newValue=e.target.value;
this.setVal(vm,expr,newValue)
})
updateFn&&updateFn(node,this.getVal(vm,expr))
},
updater:{
//文本更新
textUpdater(node,value){
node.textContent=value
},
//输入框更新
modelUpdater(node,value){
node.value=value
}
}
}
watcher.js:
//观察者的目的就是给需要变化的dom元素增加一个观察者,
//当数据变化后执行对应的方法
class Watcher{
constructor(vm,expr,cb){
this.vm=vm
this.expr=expr
this.cb=cb
//先获取一下老的值
this.value=this.get()
}
getVal(vm,expr){
expr=expr.split('.')//[a,v,c,s,a,w,r]
return expr.reduce((prev,next)=>{//vm.$date.a
return prev[next]
},vm.$data)
}
get(){
Dep.target=this;
let value=this.getVal(this.vm,this.expr)
Dep.target=null
return value;
}
update(){
let newValue=this.getVal(this.vm,this.expr)
let oldValue=this.value
if(newValue!=oldValue){
this.cb(newValue)//调用watch的callback
}
}
}