继续:从0触摸实现Vue精简版---(对象劫持)从0触摸实现Vue精简版---(数组劫持)1.如果用户传入el,调用$mount方法。在数据上inintState后,如果用户配置了el属性,数据会通过调用$mount方法渲染到页面上。这时:Vue.prototype._init=function(options){//vue中的初始化this.$options表示Vue中的参数letvm=this;vm.$options=选项;//MVVM原理要求数据重新初始化initState(vm);+if(vm.$options.el){+vm.$mount();+}}$mount这时候$mount需要做两件事:获取DOM元素通过用户配置的el字段,并将元素挂载到vm.$el字段上;实例化一个渲染观察者来渲染页面;functionquery(el){if(typeofel==='string'){returndocument.querySelector(el);};returnel;}//渲染页面并挂载组件Vue.prototype.$mount=function(){letvm=this;让el=vm.$options.el;//获取元素el=vm.$el=查询(el);//获取当前挂载的节点vm.$el是我要挂载的元素//通过watcher渲染渲染letupdateComponent=()=>{//更新渲染逻辑vm._update();//更新组件}newWatcher(vm,updateComponent);//RenderWatcher,默认会第一次调用updateComponent}这里会生成一个renderingWatcher的实例。我们先简单实现一下Watcher类,在observe目录下新建一个watcher.js:letid=0;//Watcher唯一标识classWatcher{//每生成一个watch,都会有一个唯一标识/****@param{*}vmnewVue当前渐变实例*@param{*}exprOrFn一个表达式用户可能传入的或者一个函数*@param{*}cb用户传入的回调函数vm.$watch('msg',cb)*@param{*}选择一些其他参数*/constructor(vm,exprOrFn,cb=()=>{},opts={}){this.vm=vm;this.exprOrFn=exprOrFn;if(typeofexprOrFn==='function'){this.getter=exprOrFn;}这个.cb=cb;this.opts=opts;这个.id=id++;这个.get();}get(){这个.getter();//让传入函数执行}}exportdefaultWatcher;根据目前的Watcher实现,当生成这个渲染Watcher的新实例时,默认会执行UpdateComponent方法,也就是会执行vm._update方法,如下我们来看看_更新方法。2、_update_update方法主要实现页面更新,将编译好的DOM插入到对应的节点中。这里我们暂时不介绍虚拟DOM的方法。我们先用更简单的方法来实现文字渲染。先使用createDocumentFragment将所有节点裁剪到内存中,然后编译内存中的文档片段。Vue.prototype._update=function(){让vm=this;让el=vm.$el;/**TODO虚拟DOM重写*///匹配{{}}替换letnode=document.createDocumentFragment();让第一个孩子;while(firstChild=el.firstChild){node.appendChild(firstChild);}编译器(节点,虚拟机);//编译匹配{{}}文本的节点内容,将其替换为变量el.appendChild(node)的值;}让我们实现编译器方法:3.编译器方法实现constdefaultRE=/\{\{((?:.|\r?\n)+?)\}\}/g;exportconstutil={getValue(vm,expr){//school.nameletkeys=expr.split('.');returnkeys.reduce((memo,current)=>{memo=memo[current];//相当于memo=vm.school.namereturnmemo;},vm);},/***编译文本替换{{}}*/compilerText(node,vm){node.textContent=node.textContent.replace(defaultRE,function(...args){returnutil.getValue(vm,args[1]);});}}/***文本编译*/导出函数compiler(node,vm){letchildNodes=node.childNodes;[...]childNodes].forEach(child=>{//一个是元素,另一个是文本if(child.nodeType==1){//1表示元素编译器(child,vm);//如果子元素还是非文本,则递归编译当前元素的子节点}elseif(child.nodeType==3){//3表示文本util.compilerText(child,vm);}})}好了,现在我们的节点编译方式也实现了,我们来看看页面的效果,修改index.html为Vue模板的形式: 学校名称{{school.name}} 学龄{{school.age}}
