前言:本文的核心是Vue2的指令和语句周期的架构,基于模板引擎,虚拟Dom和Diff算法,数据响应原理,抽象语法树首先,这就像盖房子。需要的砖块、水泥、钢筋都准备好了,那么接下来就是如何将它们组合起来,发挥各自的作用,让房子的框架先搭起来呢?这里简化了Vue类的创建,模拟一个手写的Vue类:Vue.jsexportdefaultclassVue{constructor(options){//todo}}Vue构造函数中传入的对象就是我们每次实例化Vue类时的elday,data,methods等index.htmlvarvm=newVue({el:'#app',data:{a:1,b:{c:2}},watch:{a(){console.log('变化')}}});这时候options被存储为$options,目的是让用户使用,然后让数据响应。之后会进行模板编译,会调用Compile类,options传递过去的el和context(vue实例):Vue.jsexportdefaultclassVue{constructor(options){//保存参数options对象作为$optionsthis.$options=options||{}//数据。_data=options.data||不明确的;//数据变成响应式,这里是生命周期...//模板编译newCompile(options.el,this)}}编译类-模板编译Compile.jsexportdefaultclassCompile{constructor(el,vue){//vue实例this.$vue=vue;//挂载点this.$el=document.querySelector(el);//如果用户传入挂载点if(this.$el){//调用函数将节点变成片段(fragment),类似于mustachetokens其实就是用了AST,这里是轻量级的,片段让$fragment=this.node2Fragment(this.$el);//compilethis.compile($fragment)//替换后的内容应该上传到树中this.$el.appendChild($fragment)}}}fragment(片段)在Compile类中生成,createnode2Fragment()方法,目的是让所有dom节点进入fragmentCompile.jsnode2Fragment(el){//createDocumentFragment创建一个virtual节点对象varfragment=document.createDocumentFragment();//console.log(片段)varchild;//让所有dom节点进入片段while(child=el.firstChild){fragment.appendChild(child)}returnfragment}compile()方法编译片段。在Compile类的compile()方法中,循环遍历上一步中fragment片段的每个元素。Vue源码中处理了虚拟节点和diff。这里使用Fragment来简单表达主要思想。Compile.jscompile(el){console.log(el)//获取子元素varchildNodes=el.childNodes;//上下文变量self=this;varreg=/\{\{(.*)\}\}/;childNodes.forEach(node=>{lettext=node.textContent;if(node.nodeType==1){self.compileElement(node)}elseif(node.nodeType==3&®.test(text)){letname=text.match(reg)[1]self.compileText(node,name)}})}根据节点的类型,对节点进行不同的处理。节点类型为1,说明是标签,然后调用compileElement()方法~compileElement()方法分析指令,进一步分析这个标签,看标签上有没有属性,再看有没有vue指令在属性列表中。Compile.jscompileElement(node){//console.log('node',node)//这里方便的地方在于不把html结构当成字符串,而是真正的属性列表varnodeAttrs=node.attributes;//console.log('nodeAttrs',nodeAttrs)varself=this;//类数组对象变成数组Array.prototype.slice.call(nodeAttrs).forEach(attr=>{//这里是分析指令varattrName=attr.name;varvalue=attr.value;//所有指令都以v-开头vardir=attrName.substring(2);//检查它是否是一条指令if(attrName.indexOf('v-')==0){//v-以命令开头if(dir=='model'){newWatcher(self.$vue,value,value=>{node.value=value});varv=self.getVueVal(self.$vue,value);node.value=v;node.addEventListener('input',e=>{varnewVal=e.target.value;self.setVueVal(self.$vue,value,newVal)v=newVal;})}elseif(dir=='for'){}elseif(dir=='if'){console.log('ifinstruction')}}})}在上面的v-model指令中处理的时候,用这个指令监听载波输入,返回定义Vue类。必须是classconstructor中的数据处理是responsive和watchVue.jsexportdefaultclassVue{constructor(options){...observe(this._data)//_initData()方法是让默认数据变成Responsive,这里是life循环this._initData();//this._initComputed();//计算数据//模板编译newCompile(options.el,this)//options.created()//Vue主动调用用户传入的生命周期函数}_initData(){varself=this;Object.keys(this._data).forEach(key=>{Object.defineProperty(self,key,{get(){returnself._data[key];},set(newVal){self._data[key]=newVal;}})})}}如果在Vue实例中监听data中的某个属性,那么在Vue构造函数中,需要初始化watchVue.js_initWatch(){varself=this;varwatch=this.$options.watch;Object.keys(watch).forEach(key=>{newWatcher(self,key,watch[key])})}此时打印Vue实例vm,会发现data中的a属性有个__ob__Observer类的属性,__ob__中有一个dep,这取决于采集系统,如图:此时Vue的responsive部分大致模拟写好了,但是fragment还没有上传到树中,所以compile()中的nodeType为3,说明是文本,处理Compile.jscompileText(node,name){console.log('name',name)node.textContent=this.getVueVal(this.$vue,name);newWatcher(this.$vue,name,value=>{node.textContent=value;});}getVueVal(vue,exp){varval=vue;exp=exp.split('.');exp.forEach(k=>{val=val[k];})returnval;}setVueVal(vue,exp,value){varval=vue;exp=exp.split('.');exp.forEach((k,i)=>{if(i
