AST结构AST是抽象语法树,在virtualdom、eslint、babel中都接触过。简单的说,就是一个描述dom的数据结构。Dom可以通过AST还原,dom也可以转换为AST。因为是树结构,所以必须有一个children字段来保存子节点,还有一个parent字段来保存父节点。也就是说,有tagname,节点类型,type=1表示普通节点类型,type=3表示普通文本类型,type=2表示带插值的文本类型。提供一个函数createASTElement来生成dom节点,后面会用到。exportfunctioncreateASTElement(tag,parent){return{type:1,tag,parent,children:[],};}stack因为dom一对一出现,一对可能包含多对,所以用括号匹配同理,这里也少不了栈。遇到开始标签就入栈,遇到结束标签就出栈,这样栈顶元素就可以一直是后续节点的父节点.例如,
3<5?
。1.divspan3<5/spanspan吗?/span/div^stack:[div],栈顶的div,后面的节点是div2的子节点。是否divspan3<5/spanspan?/span/div^stack:[div,span],当前栈顶的span,后面的节点是span的子节点3,是否divspan3<5/spanspan?/span/div^stack:[div,span],当前栈span的栈顶,3<5是否属于span4,divspan3<5/span是否span?/span/div^stack:[div],当遇到结束节点span时,将栈顶的span去掉,后面的节点为div的子节点5、是divspan3<5/span跨度?/span/div^stack:[div,span],当前栈顶span,后面的节点是span的子节点6,是否divspan3<5/spanspan?/span/div^stack:[div,span],当前栈顶跨度,?是否属于span7,divspan3<5?去掉span,后面的节点是div的子节点8,是不是divspan3<5/spanspan?/span/div^stack:[],当遇到结束节点div时,移除栈顶的div,整体算法在遍历结束时添加一个栈变量,添加currentParent保存当前父节点,添加root保存根节点。让root;让currentParent;让stack=[];接下来完善模板编译分词中留下的start、end、chars三个回调函数。parseHTML(template,{start:(tagName,unary,start,end)=>{console.log("Starttag:",tagName,unary,start,end);},end:(tagName,start,end)=>{console.log("Endtag:",tagName,start,end);},chars:(text,start,end)=>{console.log("Text:",text,start,end);},});开始函数开始:(tagName,unary,start,end)=>{letelement=createASTElement(tagName,currentParent);如果(!根){根=元素;}if(!unary){currentParent=element;stack.push(元素);}else{closeElement(元素);}},首先调用createASTElement生成一个AST节点,如果当前是第一个起始节点,则给root赋值,然后判断是否为酉节点。如果是单元素节点,直接调用closeElement将当前节点添加到父节点。functioncloseElement(element){if(currentParent){currentParent.children.push(element);element.parent=currentParent;}}endfunctionend:(tagName,start,end)=>{constelement=stack[stack.length-1];//出栈stack.length-=1;currentParent=stack[stack.length-1];closeElement(element);},弹出栈,更新当前父节点,将弹出的元素添加到节点中的父节点。chars函数chars:(text,start,end)=>{if(!currentParent){return;}constchildren=currentParent.children;如果(文本){让孩子={类型:3,文本,};children.push(孩子);}},这里只考虑类型为3的普通文本节点。整体代码结合模板编译分词中实现的分词。整体代码如下:constunicodeRegExp=/a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;constncname=`[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}]*`;constqnameCapture=`((?:${ncname}\\:)?${ncname})`;conststartTagOpen=newRegExp(`^<${qnameCapture}`);conststartTagClose=/^\s*(\/?)>/;constendTag=newRegExp(`^<\\/${qnameCapture}[^>]*>`);exportfunctioncreateASTElement(tag,parent){return{type:1,tag,parent,children:[],};}exportfunctionparseHTML(html,options){让索引=0;while(html){lettextEnd=html.indexOf("<");if(textEnd===0){//开始标签:conststartTagMatch=parseStartTag();如果(startTagMatch){handleStartTag(startTagMatch);继续;}//结束标签:varendTagMatch=html.match(endTag);如果(endTagMatch){varcurIndex=index;提前(endTagMatch[0].length);parseEndTag(endTagMatch[1],curIndex,索引);继续;}}lettext,rest,next;如果(textEnd>=0){rest=html.slice(textEnd);while(!endTag.test(rest)&&!startTagOpen.test(rest)){//<在纯文本中,宽容并将其视为文本next=rest.indexOf("<",1);如果(下一个<0)中断;textEnd+=下一个;rest=html.slice(textEnd);}text=html.substring(0,textEnd);}if(textEnd<0){text=html;}if(text){advance(text.length);}if(options.chars&&text){options.chars(text,index-text.length,index);}}functionadvance(n){index+=n;html=html.substring(n);}functionparseStartTag(){conststart=html.match(startTagOpen);if(start){constmatch={tagName:start[1],attrs:[],start:index,};提前(开始[0].length);letend=html.match(startTagClose);如果(结束){match.unarySlash=结束[1];提前(结束[0]。长度);match.end=索引;返回匹配;}}}functionhandleStartTag(match){consttagName=match.tagName;constunarySlash=match.unarySlash;constunary=!!unarySlash;options.start(tagName,unary,match.start,match.end);}functionparseEndTag(tagName,start,end){options.end(tagName,start,end);}}常量plate="3<5吗?
";console.log(template);functionparse(template){letroot;让当前的父母;让堆栈=[];functioncloseElement(element){if(currentParent){currentParent.children.push(element);element.parent=currentParent;}}parseHTML(template,{start:(tagName,unary,start,end)=>{letelement=createASTElement(tagName,currentParent);if(!root){root=element;}if(!unary){currentParent=element;stack.push(element);}else{closeElement(element);}},end:(tagName,start,end)=>{constelement=stack[stack.length-1];//popstack堆栈。length-=1;currentParent=stack[stack.length-1];关闭元素(元素);},chars:(text,start,end)=>{if(!currentParent){返回;}constchildren=currentParent.children;如果(文本){让孩子={类型:3,文本,};children.push(孩子);}},});returnroot;}constast=parse(template);console.log(ast);输入3<5?
最终生成的AST结构如下:总结这篇文章用最简单的情况实现了AST的生成,了解了整个结构,下一篇将通过AST生成render函数。