Skip to content

Repository files navigation

实现属于自己的MVVM


license

 思路 avatar

  1. index.html
<body><divid="app"><inputtype="text" v-model="msg.a"><h1v-text="msg.a"></h1><div></div><ul><li>1</li><li>{{}}</li></ul><p>{{msg.a}} {{msg.b}}</p></div><scriptsrc="./watcher.js"></script><scriptsrc="./observer.js"></script><scriptsrc="./compile.js"></script><scriptsrc="./MVVM.js"></script><script>letvm=newMVVM({el: "#app",data: {msg: {a: "hello world",b: "你好"},b: null,c: {c_1: "wait..."}}})</script></body>
  1. MVVM.js
classMVVM{constructor(options){this.$el=options.el;this.$data=options.data;if(this.$el){// 编译之前,对数据进行劫持newObserver(this.$data);this.proxyData(this.$data)// 用数据和元素进行编译newCompile(this.$el,this);}}}
  1. compile.js(模板的编译)
classCompile{constructor(el,vm){this.$el=this.isElementNode(el) ? el : document.querySelector(el);this.vm=vm;// 元素存在进行编译if(this.$el){// 1.把真实的DOM放入到内存中 fragmentletfragment=this.node2fragment(this.$el);// 2.编译 元素节点 v-model 文本节点 {{}}this.compile(fragment);// 3.把编译好的fragment放回页面中this.$el.appendChild(fragment);}}// 节点转成文档碎片node2fragment(el){letfragment=document.createDocumentFragment();letfirstChild=null;while(firstChild=el.firstChild){fragment.appendChild(firstChild);}returnfragment;}// 编译元素compileElement(node){letattrs=node.attributes;Array.from(attrs).forEach(attr=>{// 判断属性名字是否包含v-letattrName=attr.name;if(this.isDirective(attrName)){// 取到对应的值放到节点中 node this.vm.$data exprletexpr=attr.value;// let type = attrName.slice(2);let[,type]=attrName.split("-");CompileUtil[type](node,this.vm,expr);}})}// 编译文本compileText(node){
...
}// 进行编译compile(fragment){letchildNodes=fragment.childNodes;[...childNodes].forEach(node=>{if(this.isElementNode(node)){// 元素节点,需要递归this.compileElement(node);this.compile(node);}else{// 文本this.compileText(node);}})}}CompileUtil={text(node,vm,expr){
...
},model(node,vm,expr){letupdateFn=this.updater['modelUpdater'];updateFn&&updateFn(node,this.getVal(vm,expr));},updater: {// 文本更新textUpdater(node,value){node.textContent=value;},// 输入框更新modelUpdater(node,value){node.value=value;}}}
  1. observer.js(数据劫持)
classObserver{constructor(data){this.observer(data);}observer(data){// data不是对象或者为null,直接returnif(!data||typeof(data)!=='object'){return;}// 要将数据进行劫持Object.keys(data).forEach((key)=>{this.defineReactive(data,key,data[key]);this.observer(data[key]);// 递归深度劫持})}// 定义响应式defineReactive(obj,key,value){let_this=this;Object.defineProperty(obj,key,{enumerable: true,configurable: true,get(){returnvalue;},set(newVal){if(newVal!==value){_this.observer(newVal);// 如果设置的值是对象,还需要从进行劫持value=newVal;}}})}}
  1. watcher.js(观察者)
classWatcher{constructor(vm,expr,cb){this.vm=vm;this.expr=expr;this.cb=cb;// 先获取旧值this.value=this.get();}getVal(vm,expr){// 获取实例上对应的数据returnexpr.split(".").reduce((prev,next)=>{returnprev[next];},vm.$data)}get(){letval=this.getVal(this.vm,this.expr);returnval;}// 对外暴露的方法update(){letnewVal=this.getVal(this.vm,this.expr);letoldVal=this.value;if(newVal!==oldVal){this.cb(newVal);// 调用watch的callback}}}

About

简易版mvvm 数据劫持+发布订阅

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages