编译器这是什么? 简单的一点是将源**转换为目标**,详细的点是将用人们易于编写、阅读和维护的高级计算机语言编写的源**程序翻译成由计算机解释和运行的低级机器语言的程序。
Vue 的编译器大致分为三个阶段,即词法分析>句法分析>生成。 词法分析阶段大致是将字符串模板解析为标记,句法分析在词法分析的基础上生成AST,**生成根据AST**生成最终。 在本文中,我们将简要分析词汇分析的过程。
在源端(src 编译器索引。js)由这样的句子**,包含parse
ast
关键词,你知道的parse
该函数用于解析模板字符串并生成它ast
目标。
const ast = parse(template.trim(),options)
发现parse
函数,发现在内部调用parsehtml
实际上parsehtml
该函数用于词法分析。 而parse
函数最终是在词法分析的基础上生成的ast
/** convert html string to ast.* export function parse ( parse 函数是在词汇分析的基础上做句法分析,生成一个 AST 的函数。 template: string, options: compileroptions): astelement | void .
那我们去看看吧parsehtml
如何读取字符流并逐步解析模板字符串。
export function parsehtml (html, options) if (textend < 0) if (options.chars &&text) else,因为 while 循环会调用 advance 来更新 html 如果上面的处理等于 2,则表示 html 在通过循环的 ** 后没有变化,此时的 html 字符串被视为纯文本 如果 (html === last) 整个字符串被视为文本。") break } 调用 parseendtag 函数 parseendtag() advance 函数删除解析字符串 function advance (n) parsestarttag 函数 parsestarttag 函数 parsestarttag () handlestarttag 函数处理 parsestarttag 结果 function handlestarttag (match) parseendtag 函数 parseendtag (tagname, start, end) }
从上面可以看出,当数组为空或标签为纯文本标签(style、script、textarea)时,得到
有三种类型的情况,其中第一次出现在字符串中。
1. 在 textend === 0(出现在第一个位置),以 comment 节点和 start 标签为例,在内部简单说明如何处理 textend === 0 的情况。
注释节点。
if (comment.test(html)) advance(commentend + 3) 调用 advance 函数传入解析字符串的结束位置,删除已经处理过的 HTML,将 HTML 变量更新为剩余的未处理字符串,将 indexd 值更新为 commentend + 3(读取 html 字符串的地方) 继续跳出这个循环 开始下一个循环,再次开始解析 Process。 }
“开始”选项卡。
const starttagmatch = parsestarttag() 调用 parsestarttag 函数,并获取其返回值,如果有返回值,则表示 start 标签解析成功,确实是 start 标签 if (starttagmatch) continue}function parsestarttag ()advance(start[0].长度)这里传入 tagname 标签的长度并调用高级函数 let end, attr while 循环执行的条件是它与开始标签的结束部分不匹配,并且它与 start 标签中的属性 while (!) 匹配end = html.match(starttagclose)) attr = html.match(属性)))if (end) handlestarttag (match) if (canbeleftopentag(tagname) &lasttag === tagname) const unary = isunarytag(tagname) |unaryslash 确定开始标记是否为一元标记 const l = matchattrs.长度 const attrs = new array(l) for 循环用于格式匹配attrs 数组并将格式化数据存储在常量 attrs 中 for (let i = 0; i < l; i++)if (args[4] === '') if (args[5] === '') const value = args[3] |args[4] |args[5] |''attrs[i] = attrs 是一个数组 } if (!一元) )lasttag = tagname lasttag 变量保存堆栈顶部的元素 update lasttag variable } if (options.)。start)
2. 在 textend 的情况下 >= 0.
此段落处理第一个字符<但标签未成功匹配的字符串,或者第一个字符未<的字符串,let text, rest, nextif (textend >= 0) text = htmlsubstring(0, textend) 现在保证文本是纯文本 advance(textend)}if (options.)。chars &&text)
3. 在 textend <= 0 的情况下,整个 HTML 字符串将作为文本进行处理。
if (textend < 0)
上面的分析是针对最新的标签是非纯文本标签的情况,那么如何处理纯文本标签呢? 纯文本标签包括 script 标签、style 标签和 textarea 标签。
let endtaglength = 0 用于保存纯文本标签,封闭标签的字符长度 const stackedtag = lasttagtolowercase()const restackedtag = recache[stackedtag] |recache[stackedtag] = new regexp('([\s\\s]*?', 'i'restackedtag 的目的是匹配纯文本标签和结束标签的内容 ** 使用常规的 restackedtag 来匹配字符串 html 并将其替换为空字符串,常量 rest 将保存剩余字符 const rest = htmlreplace(restackedtag, function (all, text, endtag) if (shouldignorefirstnewline(stackedtag, text)) if (options.chars) return ''将正则表达式替换为与''})index += html.length - rest.length;结束标记位置是 htmllength - 剩余字符串长度 html = rest update html 以启动新的 while 循环 parseendtag(stackedtag, index - endtaglength, index)。
看完上面的段落,我才知道parseendtag
该函数尚未被分析,因此根据名称,它应该使用结束标记进行处理。
有三种方法可以调用 parseendtag() 来处理堆栈中剩余的未处理标签。 parseEndTag(TagName) ParseEndTag (TagName, Start, End) 函数 ParseEndTag (TagName, Start, End) 查找最接近的相同类型的打开标签 堆栈闪回 查找与结束标签对应的开始标签 if ( tagname) else if (pos >= 0) >没有匹配的结束标签。` if (options.end) // remove the open elements from the stack stack.length = pos 当 tagname 传入以删除 pos 后的元素时 tagname 在 pos 0 中传递时相当于清空堆栈 lasttag = pos &&&stack[pos - 1]。tag update top of stack element } else if (lowercasedtagname ===.)'br') else if (lowercasedtagname === 'p') if (options.end) pos< 遇到其他缺少开始标签,忽略结束标签的情况}
在词法分析过程中,可以通过读取字符流并用常规代码逐点解析字符串来实现,直到解析完整个字符串。 每当遇到特定的令牌时,都会调用相应的钩子函数,并传递有用的参数。 再parse
基于这些参数生成函数ast