HTML
<!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">
<input type="text"
v-model="msg">
<h1> {{msg}} </h1>
<h1 v-html="msg"></h1>
<button @click="modifyMsg">
修改msg
</button>
</div>
<script src="./test.js"></script>
</body>
</html>
JS
class Vue {
constructor(options) { // new Vue({el:"#app",...})
// 获取 dom
this.$el = document.querySelector(options.el);
// 获取 data数据 methods方法
this.$data = options.data();
this.$methods = options.methods;
// 监听事件
this.$watchEvent={};
// 代理数据 data 设置到 vm 上
this.proxyData();
// 代理函数 methods 到 vm 上
this.proxyMethods();
// 订阅事件 (如果我订阅世界 直接放到 代理数据里呢? 是不行的)
this.observe();
// 编译
this.compile(this.$el);
}
test(){
console.log("实例也能用 vm.test()");
}
proxyData(){
// 循环通过 get set 获取数据
for(let key in this.$data){
Object.defineProperty(this,key,{
get(){
return this.$data[key];
},
set(value){
this.$data[key]=value;
},
});
}
}
proxyMethods(){
// 循环通过 get set 获取数据
for(let key in this.$methods){
Object.defineProperty(this,key,{
get(){
return this.$methods[key];
},
set(value){
this.$methods[key]=value;
},
});
}
}
observe(){
for(let key in this.$data){
let dataValue = this.$data[key];
let vm = this;
Object.defineProperty(this.$data,key,{ // 这里是在 $data上设置的(有区别)
// 这里的this 会变成 ↑ 第一个传参对象
get(){
// 如果导入外层 this 会无限触发get
return dataValue;
},
set(value){
dataValue = value;
// 订阅事件
if(vm.$watchEvent[key]){
vm.$watchEvent[key].forEach((item,index,self)=>{
item.update();
});
}
},
});
}
}
compile(compileDom){
compileDom.childNodes.forEach((dom,index,arr)=>{
if(dom.nodeType===1){// HTML标签
// 是否拥有 v-html属性
if(dom.hasAttribute("v-html")){
// 获取 v-html 的 值
let vmKey = dom.getAttribute("v-html").trim();
// 判断 vm 上 是否有 vmKey(msg) 属性
if(!this.hasKey(vmKey)) return;
// 放入到标签中
dom.innerHTML=this[vmKey];
// 观察对象
let watch = new Watch(this,vmKey,dom,"innerHTML",1);
if(!this.$watchEvent[vmKey]){ // this.$watchEvent[vmKey] 为 undefined
this.$watchEvent[vmKey]=[];
}
this.$watchEvent[vmKey].push(watch);
// 删除dom 上的属性
dom.removeAttribute("v-html");
}
// 是否拥有 v-model 属性
if(dom.hasAttribute("v-model")){
// 获取 v-model 的 值
let vmKey = dom.getAttribute("v-model").trim();
// 判断 vm 上 是否有 vmKey(msg) 属性
if(!this.hasKey(vmKey)) return;
// 放到 input 的 value 里
dom.value=this[vmKey];
// 观察对象
let watch = new Watch(this,vmKey,dom,"value",1);
if(!this.$watchEvent[vmKey]){ // this.$watchEvent[vmKey] 为 undefined
this.$watchEvent[vmKey]=[];
}
this.$watchEvent[vmKey].push(watch);
// 输入事件监听
dom.addEventListener("input",(e)=>{
this[vmKey]=e.target.value;
});
// 删除dom 上的属性
dom.removeAttribute("v-model");
}
// 是否拥有 @click 属性
if(dom.hasAttribute("@click")){
// 获取 方法名字
let methodsFnName = dom.getAttribute("@click").trim();
// 判断 vm 上 是否有 methodsFnName 函数
if(!this.hasKey(methodsFnName)) return;
dom.addEventListener("click",(e)=>{
this[methodsFnName](e);
});
// 删除 @click 属性
dom.removeAttribute("@click");
}
// 是否拥有子节点 (递归)
if(dom.childNodes.length>0){
this.compile(dom);
}
}
else if(dom.nodeType===3){// 文本 {{xxx}}
if(dom.parentNode.hasAttribute("v-pre")){
// 如果 包裹 {{}} 的标签 有v-pre
// 就 跳过编译 展示 {{}}
dom.parentNode.removeAttribute("v-pre");
return;
}
let reg = /\{\{(.+?)\}\}/g;
// 普通文本 就不往下走了
if(!reg.test(dom.nodeValue))return;
let vmKey = "$2";
dom.nodeValue=dom.nodeValue.replace(reg,($1,$2)=>{
// $1 = {{msg}} $2 = msg
vmKey=$2.trim();
return this[vmKey];
});
// 观察对象
let watch = new Watch(this,vmKey,dom,"nodeValue",3);
if(!this.$watchEvent[vmKey]){ // this.$watchEvent[vmKey] 为 undefined
this.$watchEvent[vmKey]=[];
}
this.$watchEvent[vmKey].push(watch);
}
});
}
// Vue实例上 是否有 某某某 属性
hasKey(key){
if(this.hasOwnProperty(key)){
return true;
}
else{
console.error(`Vue实例上并没有${key}属性`);
return false;
}
}
};
class Watch {
constructor(vm,key,dom,attr,domType){
this.vm=vm; // 实例化的app
this.key=key; // vm触发的属性 msg age ..
this.dom=dom; // vm[key]绑定的HTML节点 <h1></h1>
this.attr=attr; // HTML节点上的属性(innerHTML)
this.domType=domType; // 节点的类型(text ... 0 1 2)
}
update(){
this.dom[this.attr]=this.vm[this.key];// 感觉不是这么改
}
}
let app = new Vue({
el: "#app",
data() {
return {
msg: "鱼也小哥哥真帅",
age:18,
};
},
methods: {
modifyMsg() {
this.msg = "一点也不帅";
}
},
});