html文件中引入自定义的MVVM.js
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<input type="text" v-model="school.name" />
{{school.name}}
<div>{{school.name}}</div>
<div>{{school.age}}</div>
<div v-html="message"></div>
<ul>
<li>1</li>
<li>2</li>
</ul>
{{getMyLove}}
<button v-on:click="change">+</button>
</div>
<script src="./自定义mvvm.js"></script>
<script>
let vm=new Vue({
el:"#app",
data:{
school:{
name: "宋杨",
age:22
},
message:"<h1>大家好</h1>"
},
computed:{
getMyLove(){
return this.school.name+"喜欢琳琳";
}
},
methods:{
change(){
this.school.age=this.school.age+1;
//为什么这样写不行 this.school.age=this.school.age++;
}
}
})
</script>
</body>
</html>
我们需要先定义几个关键的类:
Vue:整个框架的入口。
Observer:把$data数据全部转化成Object.defineProperty()来定义。数据劫持。
Watcher:观察者,每个使用到数据的地方都要创建观察者,并且绑定到对应的被观察者身上,该过程就是订 阅。
Dep:是一个包含发布和订阅的功能,data里面的每个数据都需要有一个Dep实例。
Compiler:用来编译模版的,简单实现了虚拟DOM的功能。
2.简述一下大致的流程。
首先,进入的是vue实例,在里面获取对应的el、data、computed、method等数据。先把data的数据全部转化成Object.defineProperty()定义。然后把computed、method这些方法添加代理,例如,用this.XXX就能指代this.computed.XXX()。再把数据获取操作vm.$data上的取值操作 都代理到 vm上。最后就可以创建Compiler实例编译模版了。
注意:代理操作其实是更方便新手入门,更简单的操作数据。
Observer类里面会将data对象里面的属性全部用Object.defineProperty()定义,若属性也是对象,就把该属性里面的子属性也重定义,使用了递归的思想。同时,在每一个的get方法里面都有订阅观察者的功能,在每一个set方法里面都有发布功能。
Dep就是在Observer类中使用订阅和发布功能时候需要调用的类。
Compiler模版编译中,先获取到对应的根节点,用document。createDocumentFragment()创建文档碎片,将根结点的child都用append移动到文档碎片中。这样的话就相当于是虚拟Dom,最后使用的时候把该文档碎片塞回到页面中。
同时分别对元素和文本编译,获取到对应的指令或{{}},对v-model和{{}}都要添加观察者,并且把对应的值传到页面中。
MVVM.js
class Compiler {
constructor(el, vm) {
// 判断el是字符串还是元素。如果是字符串,通过字符串获取相应元素
this.el = this.isElementNode(el) ? el : document.querySelector(el);
// 把当前节点中的元素 获取到 放到内存储中
this.vm = vm;
let fragment = this.createFragment(this.el);
// 把节点中的内容替换
// 用数据编译模版
this.compiler(fragment);
// 把内容塞到页面中
this.el.appendChild(fragment);
}
isDirective(attrName) { //判断是不是指令
return attrName.startsWith('v-');
}
compilerElement(node) { //编译元素
let attributes = node.attributes;
[...attributes].forEach(attr => {
let {name,value:expr} = attr;
if (this.isDirective(name)) {
let [,directive]=name.split('-');
let [directiveName,eventName]=directive.split(':');
//需要调用不同的指令来处理
CompilerUtil[directiveName](node,expr,this.vm,eventName);
}
})
}
compilerText(node) { //编译文本,判断文本中是否有{{}}
let content = node.textContent;
if(/\{\{(.+?)\}\}/.test(content)){
//文本节点
CompilerUtil['text'](node,content,this.vm);//{{a}} {{b}}
}
}
//核心编译方法
compiler(node) { //编译内存中dom
let childNodes = node.childNodes;
[...childNodes].forEach(child => {
if (this.isElementNode(child)) {
this.compilerElement(child);
//如果是元素的话 需要把自己传进去 再去遍历子节点
this.compiler(child);
} else {
this.compilerText(child);
}
})
}
createFragment(node) { //把节点移动到内存中
// 创建文档碎片
let fragment = document.createDocumentFragment();
let firstChild;
while (firstChild = node.firstChild) {
// appendChild()具有移动性
fragment.appendChild(firstChild);
}
return fragment;
}
isElementNode(node) { //判断是否为元素节点
return node.nodeType === 1;
}
}
CompilerUtil={
getValue(vm,expr){
return expr.split('.').reduce((data,current)=>{
return data[current];
},vm.$data)
},
setValue(vm,expr,value){
expr.split('.').reduce((data,current,index,arr)=>{
if(index==arr.length-1){
return data[current]=value;
}
return data[current];
},vm.$data)
},
on(node,expr,vm,eventName){
node.addEventListener(eventName,(e)=>{
vm[expr].call(vm,e);
});
},
model(node,expr,vm){
let fn=this.updater['modeUpdater'];
new Watcher(vm,expr,(newValue)=>{//添加观察者
fn(node,newValue);
});
node.addEventListener('input',(e)=>{
let value=e.target.value;
this.setValue(vm,expr,value);
});
let value=this.getValue(vm,expr);
fn(node,value)
},
html(node,expr,vm){
let fn=this.updater['htmlUpdater'];
new Watcher(vm,expr,(newValue)=>{//添加观察者
fn(node,newValue);
});
let value=this.getValue(vm,expr);
fn(node,value)
},
getContentValue(vm,expr){
return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getValue(vm,args[1]);
});
},
text(node,expr,vm){
let fn=this.updater['textUpdater'];
let content=expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
new Watcher(vm,args[1],()=>{//添加观察者
fn(node,this.getContentValue(vm,expr));//返回一个全的字符串
});
return this.getValue(vm,args[1]);
});
fn(node,content);
},
updater:{
modeUpdater(node,value){
node.value=value;
},
htmlUpdater(node,value){
node.innerHTML=value;
},
textUpdater(node,value){
node.textContent=value;
}
}
}
//观察者 (发布订阅) 观察者(多) 被观察者(一)
class Dep {
constructor() {
this.subs=[];//存放所有的watcher
}
//订阅
addSub(watcher){//添加watcher方法
this.subs.push(watcher);
}
//发布
notify(){
this.subs.forEach(watcher=>watcher.updata());
}
}
// vm.$watch(vm,'school.name',(newValue)=>{})
class Watcher{
constructor(vm,expr,cb){
this.vm=vm;
this.expr=expr;
this.cb=cb;
this.oldValue=this.get();
}
get(){
//取数据之前吧当前的观察者放到Dep.target里面
Dep.target=this;
//由于getValue()会触发Object.defineProperty的get方法,在get里面会将此watcher添加到所属的Dep实例里面。
let value=CompilerUtil.getValue(this.vm,this.expr);
//取完数据之后必须赋空,否则后面其他地方会无法更新Dep.target的值,意味着后面的订阅都会出错。
Dep.target=null;
return value;
}
updata(){
let newValue =CompilerUtil.getValue(this.vm,this.expr);
if(newValue != this.oldValue){
this.cb(newValue);
}
}
}
class Observer{
constructor(data) {
this.observer(data);
}
// 实现数据劫持功能
observer(data){
//如果是对象才观察
if(data && typeof data == 'object'){
//如果是对象
for(let key in data){
this.defineReactive(data,key,data[key]);
}
}
}
defineReactive(obj,key,value){
this.observer(value);
let dep =new Dep();//给每一个属性都加上具有发布和订阅的功能
Object.defineProperty(obj,key,{
get(){
Dep.target && dep.addSub(Dep.target);
return value;
},
set:(newValue)=>{
if(value != newValue){
// 赋的新值,需要把它再监控下
this.observer(newValue);
value=newValue;
dep.notify();
}
}
});
}
}
class Vue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
let computed=options.computed;
let methods=options.methods;
//根结点存在,编译模版
if (this.$el) {
// 把数据全部转化成Object。defineProperty()来定义
new Observer(this.$data);
for(let key in computed){
Object.defineProperty(this.$data,key,{
get:()=>{
return computed[key].call(this)
}
});
}
for(let key in methods){
Object.defineProperty(this,key,{
get(){
return methods[key];
}
});
}
// 把数据获取操作 vm上的取值操作 都代理到vm.$data
this.proxyVm(this.$data);
new Compiler(this.$el, this);
}
}
proxyVm(data){
for(let key in data){
Object.defineProperty(this,key,{
get(){
return data[key]; //进行转化
},
set(newValue){
data[key]=newValue;
}
});
}
}
}