classWatcher{//通过回调函数constructor(expr,vm,cb)将更新的数据通知给视图{this.expr=expr;这个.vm=虚拟机;这个.cb=cb;this.oldVal=this.getOldVal();}//获取旧数据getOldVal(){//使用getValue获取数据调用getter()方法时,先挂载当前观察者Dep.target=this;constoldVal=compileUtil.getValue(this.expr,this.vm);//挂载后需要注销,防止重复挂载(数据一更新就会挂载)Dep.target=null;返回旧值;}//通过回调函数更新数据update(){constnewVal=compileUtil.getValue(this.expr,this.vm);如果(newVal!==this.oldVal){this.cb(newVal);}}}//Dep类存储watcher对象,当数据变化时通知watcherclassDep{constructor(){this.watcherCollector=[];}//添加观察者addWatcher(watcher){this.watcherCollector.push(watcher);}//当数据改变时通知观察者更新notify(){this.watcherCollector.forEach(w=>w.update());}}//定义观察者类Observer{constructor(data){this.observe(data);}//data是一个对象,可能会嵌套其他对象,需要通过递归遍历的方式来进行观察者绑定observe(data){if(data&&typeofdata==='object'){Object.keys(data).forEach(key=>{this.defineReactive(data,key,data[key]);})}}//传递对象。defineProperty方法劫持对象属性defineReactive(obj,key,value){//递归观察this.observe(value);constdep=newDep();Object.defineProperty(obj,key,{enumerable:true,configurable:false,get(){//订阅数据变化时,给DevDep.target添加观察者&&dep.addWatcher(Dep.target);returnvalue;},//在定义Domainset时使用箭头函数绑定this的定义:(newVal)=>{if(value===newVal)return;this.observe(newVal);value=newVal;//通知watcherthatdatachangesdep.notify();}})}}//编译模板的具体执行constcompileUtil={getValue(expr,vm){//处理person.name的对象类型,取出real值返回expr.split('.').reduce((data,currentVal)=>{返回数据[currentVal];},vm.$data)},setVal(expr,vm,inputValue){expr.split('.').reduce((data,currentVal)=>{data[currentVal]=inputValue;},vm.$data)},getContent(expr,vm){//{{person.name}}--{{person.age}}//防止修改person.name使所有值都被替换returnexpr.replace(/\{\{(.+?)\}\}/g,(...args)=>{returnthis.getValue(args[1],vm);});},text(node,expr,vm){让值;if(expr.indexOf('{{')!==-1){value=expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{//Text的Watcher应该绑定在这里,因为是插值的双向绑定{{}}//Watcher的构造函数的getOldVal()方法需要接受数据或对象,但是{{person.name}}不能接收新的Watcher(args[1],vm,()=>{this.updater.textUpdater(node,this.getContent(expr,vm));});返回this.getValue(args[1],vm);});}else{value=this.getValue(expr,vm);}这。updater.textUpdater(节点,值);},html(node,expr,vm){letvalue=this.getValue(expr,vm);//html对应的WatchernewWatcher(expr,vm,(newVal)=>{this.updater.htmlUpdater(node,newVal);})this.updater.htmlUpdater(node,value);},model(node,expr,vm){constvalue=this.getValue(expr,vm);//V-model绑定对应的Watcher,数据驱动视图newWatcher(expr,vm,(newVal)=>{this.updater.modelUpdater(node,newVal);});//视图=>数据=>视图节点。addEventListener('input',(e)=>{this.setVal(expr,vm,e.target.value);})this.updater.modelUpdater(node,value);},on(node,expr,vm,detailStr){让fn=vm.$options.methods&&vm.$options.methods[expr];node.addEventListener(detailStr,fn.bind(vm),false);},bind(node,expr,vm,detailStr){//v-on:href='...'=>href='...'node.setAttribute(detailStr,expr);},//视图更新函数数据更新器:{textUpdater(node,value){node.textContent=value;},htmlUpdater(node,value){node.innerHTML=value;},modelUpdater(node,value){node.value=value;}}}//编译HTML模板对象classCompiler{constructor(el,vm){this.el=this.isElementNode(el)?el:文档.querySelector(el);这个.vm=虚拟机;//1.将预编译的Element节点放入文档片段对象中,避免DOM的频繁回流和重绘,提高渲染性能constfragments=this.node2fragments(this.el);//2.编译模板this.compile(fragments);//3.将子元素附加到根元素this.el.appendChild(fragments);}compile(fragments){//1.获取子节点constchildNodes=fragments.childNodes;//2.递归循环compilation[...childNodes].forEach(child=>{//如果是元素节点if(this.isElementNode(child)){this.compileElement(child);}else{//文本节点this.compileText(child);}//递归遍历if(child.childNodes&&child.childNodes.length){this.compile(child);}})}compileElement(node){letattributes=node.attributes;//遍历编译每个属性//属性是类数组,所以需要先转数组[...attributes].forEach(attr=>{let{name,value}=attr;//v-text="msg"v-html=htmlStrtype="text"v-model="msg"if(this.isDirector(name)){//v-textv-htmlv-modev-bindv-on:clickv-bind:href=''let[,directive]=name.split('-');let[compileKey,detailStr]=directive.split(':');//更新数据,数据驱动视图compileUtil[compileKey](node,value,this.vm,detailStr);//删除带有directive属性的标签v-textv-html等,普通value等原生html标签不需要删除node.removeAttribute('v-'+directive);}elseif(this.isEventName(name)){//如果是事件处理@click='handleClick'let[,detailStr]=name.split('@');compileUtil['on'](节点,VAlue,this.vm,detailStr);node.removeAttribute('@'+detailStr);}})}compileText(node){//{{person.name}}--编译文本中的{{person.age}}constcontent=node.textContent;if(/\{\{(.+?)\}\}/.test(content)){compileUtil['text'](node,content,this.vm);}}isEventName(attrName){//判断@是否以开头returnattrName.startsWith('@');}isDirector(attrName){//判断是否为Vue特性标签returnattrName.startsWith('v-');}node2fragments(el){//创建文档片段对象constf=document.createDocumentFragment();让第一个孩子;while(firstChild=el.firstChild){f.appendChild(firstChild);}返回f;}isElementNode(node){//元素节点的nodeType属性为1returnnode.nodeType===1;}}classMVue{constructor(options){//初始元素和数据通过选项对象绑定this.$el=options.el;this.$data=options.data;这个。$选项=选项;//通过Compiler对象编译模板,如{{}}插值、v-text、v-html、v-model等Vue语法if(this.$el){//1.创建观察者newObserver(this.$data);//2.编译模板newCompiler(this.$el,this);//3.通过数据代理实现this.person.name='海贼王——路飞'的功能,而不是this.$data.person.name='海贼王——路飞'this.proxyData(this.$数据);}}//使用vm代理vm.$dataproxyData(data){for(letkeyindata){Object.defineProperty(this,key,{get()){returndata[key];},set(newVal){数据[key]=newVal;}})}}}
