从源码层级理解MVVC模式
观看b站视频——vue源码解析后写的笔记

<body>
<div id="app">
<h1>{{my.name}}-{{my.fav}}</h1>
<p v-html="str"></p>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>{{my.age}}</li>
</ul>
<span v-text="my.fav"></span>
<span v-text="msg"></span>
<h2>{{msg}}</h2>
<h3 v-on:click="handleClick">点我打印</h3>
<input type="text" v-model="msg">
</div>
</body>
<script src="./MVue.js"></script>//编译器
<script src="./Observer.js"></script>//观察者
<script>
let vm= new MVue({
el:'#app',
data:{
my:{
name:'咔酱',
fav:'爆炸',
age:'16'
},
msg:'西内',
str:'<h1>爆杀地</h1>'
},
methods:{
handleClick(){
console.log('consoleCLick')
}
}
})
</script>
整体思路是:
Compile 解析器
1.用document.fragment创建文本流把我们要改动的节点append进去,减少页面回流和重绘
2.获取节点属性,如果节点属性有指令,根据不同指令,调用不同解析方法,解析方法替换值
3.把替换完毕的文档流放入el(id="app"的节点)下。
class MVue{
constructor(options){//options:vm实例
this.$el=options.el;
this.$data=options.data;
this.$options=options;
if(this.$el){//如果传了一个根节点
//1.实现一个观察者
//2.实现一个指令解析器 compile
new Compile(this.$el,this);
}
}
}
创建文本流和遍历节点属性拿出其中指令并调用对应update方法
class Compile{
constructor(el,vm){
this.el=this.isElementNode(el)?el:document.querySelector(el);
//如果它是一个元素节点,如果不是就获取
this.vm=vm;
//1.创建一个文档碎片对象,把我们要改动的节点append进去,减少页面回流和重绘
const fragment=this.node2Fragement(this.el);
//2.编译节点中我们的指令
this.compile(fragment);
//3.把文档碎片放入el下
this.el.appendChild(fragment);
}
//1.创建文本流,并遍历el下子节点加入其中
node2Fragement(el){
let firstChild;
let f=document.createDocumentFragment();
while(firstChild=el.firstChild){
f.appendChild(firstChild);
}
return f;
}
//2.解析文本流,遍历其中节点,分文本节点和元素节点,如果节点中有子节点,递归调用compile
compile(fragment){
const childNodes=fragment.childNodes;
childNodes.forEach(child=>{
if(this.isElementNode(child)){
//是元素节点
this.compileElement(child);
}else{
//文本节点
this.compileText(child);
}
//递归
if(child.childNodes && child.childNodes.length){
this.compile(child);
}
})
//childNodes 打印为一个数组 NodeList(15)[text, h1, text, p, text, ul, text, span, text, span, text, h2, text, input, text]
}
isElementNode(el){
return el.nodeType===1;//如果是节点,nodeType属性===1 原生js
}
//3.1处理元素节点:拿到每个节点的属性,拿到属性,判断属性是否含有指令,根据不同指令使用不同update方法
compileElement(node){
const attributes=node.attributes;
[...attributes].forEach(attr=>{
const {name,value}=attr;
if(this.isDirective(name)){//如果是一个指令 v-text v-html on:click ...
const [,dirctive]=name.split('-');//text html on:click
const [dirName,eventName]=dirctive.split(':');//[text], [on,click]
//更新数据 数据驱动视图
compileUnit[dirName](node,value,this.vm,eventName);
// compileUnit:集合了各种指令update方法的对象
//哪个节点,指令后跟的值,当前实例,事件名
//删除指令属性
node.removeAttribute('v-'+dirName);
}
})
}
isDirective(attrName){
return attrName.startsWith('v-');
}
//3.2处理文本节点
compileText(node){
const content=node.textContent;
if(/\{\{(.+?)\}\}/.test(content)){//替换{{}}的指令
compileUnit['text'](node,content,this.vm);
}
}
}
不同指令的对应update方法
const compileUni={
//根据msg,my.fav从vm.$data中取到对应的val
getval(expr,vm){
return expr.split('.').reduce((data,currentVal)=>{
return data[currentVal]
},vm.$data)
},
//处理{{}}的指令,再取得值
getContentVal(expr,vm){
return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getval(args[1],vm);
})
},
//更新view层的函数
updater:{
textUpdater(node,value){
node.textContent=value;
},
htmlUpdater(node,value){//处理html字符串
node.innerHTML=value;
},
modelUpdater(node,value){
node.value=value;//node.value 获取文本域的值
}
},
//处理文本节点
text(node,expr,vm){
let value;
if(expr.indexOf('{{')!==-1){
value= expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getval(args[1],vm);
})
}else{
value = this.getval(expr,vm)
}
this.updater.textUpdater(node,value)
},
html(node,expr,vm){
const value=this.getval(expr,vm);
this.updater.htmlUpdater(node,value);
},
model(node,expr,vm){
const value=this.getval(expr,vm);
this.updater.modelUpdater(node,value);
},
on(node,expr,vm,eventName){
let fn=vm.$options.methods&&vm.$options.methods[expr];
node.addEventListener(eventName,fn.bind(vm),false);//手动绑上vm
}
}