这篇文章给大家介绍的内容是关于什么是ast?vue源码中ast语法树的解析,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
什么是astast是指抽象语法树(abstract syntax tree),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式。vue在mount过程中,template会被编译成ast语法树。
然后,经过generate(将ast语法树转化成render function字符串的过程)得到render函数,返回vnode。vnode是vue的虚拟dom节点,里面包含标签名、子节点、文本等信息,关于vnode的学习来自:https://blog.csdn.net/qq_3626...
以
<p id="test"> 请输入:<input type="text" v-model="message"><br/></p>
parse() var stack = []; var preservewhitespace = options.preservewhitespace !== false; var root; var currentparent; var invpre = false; var inpre = false; var warned = false; function warnonce (msg){ } function closeelement (element){ } //调用parsehtml,这里对options的内容省略 parsehtml(template,options);
定义一些变量,root用于存放ast树根节点,currentparent存放当前父元素,stack用来辅助树建立的栈。接着调用parsehtml函数进行转化,传入template和options。
options的结构如下:
parsehtml()parsehtml内容大纲
last = html; //确认html不是类似<script>,<style>这样的纯文本标签 if (!lasttag || !isplaintextelement(lasttag)) { var textend = html.indexof('<');//判断html字符串是否以<开头 if (textend === 0) { // 这里的comment是vue定义的正则表达式,判断html是不是<!-- -->注释 //var comment = /^<!\--/; if (comment.test(html)) { var commentend = html.indexof('-->'); if (commentend >= 0) { if (options.shouldkeepcomment) { options.comment(html.substring(4, commentend)); } advance(commentend + 3); continue } } //判断是否处理向下兼容的注释,类似<![if !ie]> //var conditionalcomment = /^<!\[/; if (conditionalcomment.test(html)) { var conditionalend = html.indexof(']>'); if (conditionalend >= 0) { advance(conditionalend + 2); continue } } //获取<!doctype开头的标签内容 // var doctype = /^<!doctype [^>]+>/i; var doctypematch = html.match(doctype); if (doctypematch) { advance(doctypematch[0].length); continue } //判断此段html是否结束标签 // var endtag = new regexp((^<\\/" + qnamecapture + "[^>]*>)); // var qnamecapture = ((?: + ncname + \\:)? + ncname + ); // var ncname = '[a-za-z_][\\w\\-\\.]*'; var endtagmatch = html.match(endtag); if (endtagmatch) { var curindex = index; advance(endtagmatch[0].length); parseendtag(endtagmatch[1], curindex, index); continue } // 匹配开始标签,获取match对象 var starttagmatch = parsestarttag(); if (starttagmatch) { handlestarttag(starttagmatch); if (shouldignorefirstnewline(lasttag, html)) { advance(1); } continue } var text = (void 0), rest = (void 0), next = (void 0); if (textend >= 0) { rest = html.slice(textend); while ( !endtag.test(rest) && !starttagopen.test(rest) && !comment.test(rest) && !conditionalcomment.test(rest) ) { // 处理文本中的<字符 next = rest.indexof('<', 1); if (next < 0) { break } textend += next; rest = html.slice(textend); } text = html.substring(0, textend); advance(textend); } if (textend < 0) { text = html; html = ''; } if (options.chars && text) { options.chars(text); } } else { //代码省略 } if (html === last) { //代码省略 } }
parsehtml使用while循环对传进来的html进行解析。首先获取<标签索引var textend = html.indexof('<');如果textend为0 说明是标签<xx>或者</xx>,再用正则匹配是否为<!---->注释标签,如果不是,再判断是否为向下兼容放入注释,如<![if !ie]>(详情见),如果不是,再判断是否已... var end, attr; //match对象的attrs //index=14 while (!(end = html.match(starttagclose)) && (attr = html.match(attribute))) { advance(attr[0].length); match.attrs.push(attr); } // 在第二次while循环后 end匹配到结束标签 => ['>',''] if (end) { match.unaryslash = end[1]; advance(end[0].length); match.end = index; return match } } }
parsestarttag()构建一个match对象,对象里面包含标签名(tagname),标签属性(attrs),<左开始标签的位置(start),>右开始标签的位置(end)。本文的例子,程序第一次进入该函数,所以tagname:p,start:0,end:14 match
function advance (n) { index += n; html = html.substring(n); }
advance函数将局部变量index往后推 并切割字符串。
handlestarttag() function handlestarttag (match) { var tagname = match.tagname; var unaryslash = match.unaryslash; if (expecthtml) { //段落式元素 if (lasttag === 'p' && isnonphrasingtag(tagname)) { parseendtag(lasttag); } // 可以省略闭合标签 if (canbeleftopentag$$1(tagname) && lasttag === tagname) { parseendtag(tagname); } } var unary = isunarytag$$1(tagname) || !!unaryslash; var l = match.attrs.length; var attrs = new array(l); //解析html属性值{name:'id',value:'test'}的格式 for (var i = 0; i < l; i++) { var args = match.attrs[i]; if (is_regex_capturing_broken && args[0].indexof('""') === -1) { if (args[3] === '') { delete args[3]; } if (args[4] === '') { delete args[4]; } if (args[5] === '') { delete args[5]; } } var value = args[3] || args[4] || args[5] || ''; var shoulddecodenewlines = tagname === 'a' && args[1] === 'href' ? options.shoulddecodenewlinesforhref : options.shoulddecodenewlines; attrs[i] = { name: args[1], // 处理转义字符 value: decodeattr(value, shoulddecodenewlines) }; }// 将切割出来的字符串转换为ast if (!unary) { stack.push({ tag: tagname, lowercasedtag: tagname.tolowercase(), attrs: attrs }); //设置结束标签 lasttag = tagname; } if (options.start) { options.start(tagname, attrs, unary, match.start, match.end); } }
在该函数中,对match进行了二次处理,根据标签名、属性生成一个新对象,push到最开始的stack数组中。
由于匹配的是起始标签,所以也会以这个标签名结束,此处的lasttag就是p。
函数最后调用了parse内部声明的方法start
function start (tag, attrs, unary) { //检查命名空间是否是svg或者math var ns = (currentparent && currentparent.ns) || platformgettagnamespace(tag); // handle ie svg bug /* istanbul ignore if */ if (isie && ns === 'svg') { attrs = guardiesvgbug(attrs); } //创建element元素,element其实就是{type: 1, //tag: "p", //attrslist: [{name: "id", value: "test"}]], //attrsmap: makeattrsmap(attrs), //parent:undefined //children: []}的一个对象 var element = createastelement(tag, attrs, currentparent); if (ns) { element.ns = ns; } //排除script,style标签 if (isforbiddentag(element) && !isserverrendering()) { element.forbidden = true; "development" !== 'production' && warn$2( 'templates should only be responsible for mapping the state to the ' + 'ui. avoid placing tags with side-effects in your templates, such as ' + "<" + tag + "> + ', as they will not be parsed.' ); } // apply pre-transforms for (var i = 0; i < pretransforms.length; i++) { //若html里面有v-model等指令,通过pretransforms进行转换 element = pretransforms[i](element, options) || element; } if (!invpre) { // 判断是否有v-pre属性 processpre(element); if (element.pre) { invpre = true; } } //判断标签名是不是pre if (platformispretag(element.tag)) { inpre = true; } if (invpre) { processrawattrs(element); } else if (!element.processed) { // 处理v-for processfor(element); // 处理v-if processif(element); // 处理v-once processonce(element); // element-scope stuff processelement(element, options); } // 树结构的root节点处理 if (!root) { root = element; checkrootconstraints(root); } else if (!stack.length) { // allow root elements with v-if, v-else-if and v-else if (root.if && (element.elseif || element.else)) { checkrootconstraints(element); addifcondition(root, { exp: element.elseif, block: element }); } else { warnonce( "component template should contain exactly one root element. " + "if you are using v-if on multiple elements, " + "use v-else-if to chain them instead." ); } } if (currentparent && !element.forbidden) { if (element.elseif || element.else) { processifconditions(element, currentparent); } else if (element.slotscope) { // scoped slot currentparent.plain = false; var name = element.slottarget || '"default"';(currentparent.scopedslots || (currentparent.scopedslots = {}))[name] = element; } else { currentparent.children.push(element); element.parent = currentparent; } } if (!unary) { currentparent = element; stack.push(element); } else { closeelement(element); } }
对标签名进行校验,同时对属性进行更细致的处理,如v-pre,v-for,v-if等。最后调用processelement(element, options)对当前的树节点元素进行处理,具体如下:
processkey(element); // 检测是否是空属性节点 element.plain = !element.key && !element.attrslist.length; // 处理:ref或v-bind:ref属性 processref(element); //处理标签名为slot的情况 processslot(element); // 处理is或v-bind:is属性 processcomponent(element); for (var i = 0; i < transforms.length; i++) { element = transforms[i](element, options) || element; } //处理属性 processattrs(element);
start()生成element对象,再连接元素的parent和children节点,最后push到栈中,此时栈中第一个元素生成。结构如下:
接下来开始第二次循环,html变成了 请输入:<input type="text" v-model="message"></div>,因此这次解析的是文字:'请输入',具体代码分析在下一次~~~
相关文章推荐:
使用php-parser生成ast抽象语法树
以上就是什么是ast?vue源码中ast语法树的解析的详细内容。