Vue源码学习之实现模板转化成ast语法树(下)

第一步 编写正则表达式

在我们拿到模板之后,我们需要对标签、文本、属性、表达式、字符串等进行匹配,那就需要用到我们的正则表达式,如下:

// compiler/index.js

const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`);
console.log(startTagOpen);
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
const startTagClose = /^\s*(\/?)>/;
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; 

输出一下,用正则可视化工具来看一下规则如下:

  • startTagOpen:开始为<,然后里面是匹配名字div或者带命名空间的div:xxx等这样的开始标签的名字
  • endTag:结束标签</xxx>,最终匹配到的分组就是结束标签的名字
  • attribute:用来匹配属性的,属性前面可以有一些空白white space,不能是None of内的符号,中间是=,左右于两边可以有空白字符,左右单引号中间不是单引号,左右双引号中间不是双引号,也就是说第一个分组就是属性的key,value是分组3/4/5
  • startTagClose:匹配到的就是一个反斜杠/,可能是</div>或者<br/>
  • defaultTagRE:匹配的是双大括号{{}},两边是大括号,中间是换行或者回车以及任何字符

第二步 将模板转换成ast语法树

有了上面的正则之后,我们就需要去对模板进行匹配,然后将匹配到的字符串转换成ast语法树。

// compiler/index.js
// 对模板进行编译处理

......

function parseHTML(html) {const ELEMENT_TYPE = 1const TEXT_TYPE = 3const stack = [] // 用于存放元素let currentParent; // 指向栈中最后一个let rootfunction createdASTElement(tag, attrs) {return {tag,type: ELEMENT_TYPE,children: [],attrs,parent: null}}// 开始标签function start(tag, attrs) {let node = createdASTElement(tag, attrs) // 创造一个ast节点if (!root) { // 看一下是否为空树root = node // 如果为空则表示当前是树的根节点}if (currentParent) {node.parent = currentParent // 只赋了parent属性currentParent.children.push(node) // 还需要让父亲记住自己}stack.push(node)currentParent = node//currentParent为栈中的最后一个}// 文本function chars(text) { // 文本直接放到当前指向的节点中text = text.replace(/\s/g,'')&& currentParent.children.push({type: TEXT_TYPE,text,parent: currentParent})}// 结束标签function end(tag) {stack.pop() // 弹出最后一个currentParent = stack[stack.length - 1]}function advance(n) {html = html.substring(n)}function parseStartTag() {const start = html.match(startTagOpen)if (start) {const match = {tagName: start[1], // 标签名attrs: []}advance(start[0].length)// 如果不是开始标签的结束就一直匹配下去let attr, endwhile (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {advance(attr[0].length)match.attrs.push({ name: attr[1], value: attr[3] || attr[4] || attr[5] || true })}if (end) {advance(end.length)}return match}return false // 不是开始标签}while (html) { // html最开始肯定是一个 <// 如果textEnd为0说明是一个开始标签或者结束标签// 如果textEnd大于0则说明是文本结束的位置let textEnd = html.indexOf('<') // 如果indexOf的索引是0则说明是个标签if (textEnd === 0) {const starttagMatch = parseStartTag()//开始标签的匹配if (starttagMatch) { // 解析到的开始标签start(starttagMatch.tagName, starttagMatch.attrs)continue}let endTagmatch = html.match(endTag)if (endTagmatch) {advance(endTagmatch[0].length)end(endTagmatch[1])continue}}if (textEnd > 0) {let text = html.substring(0, textEnd) // 文本内容if (text) {chars(text)advance(text.length) // 解析到的文本}}}console.log(root);return root
}
export function compileToFcuntion(template) {// 1.将template转换为ast语法树let ast = parseHTML(template)// 2.生成render方法 render方法执行后的结果就是虚拟DOMconsole.log(template);
} 

思路:

1.通过 parseHTML(template)方法传入模板,将模板转换成ast语法树,解析开始标签、结束标签等内容,通过这个方法就会返回一个ast语法树。
2.在parseHTML()方法中,每解析一个标签则在模板的字符串中将其截取掉,直到为空,所以可以写一个while循环来完成
3.html肯定是<开头的,所以我们直接用indexOf方法来进行判断:如果textEnd为0说明是一个开始标签或者结束标签,如果textEnd大于0则说明是文本结束的位置。
4.如果textEnd等于0就认为他是开始标签,那就用parseStartTag方法来解析开始标签。如下图接下来就可以塞到match里面。
5.追加完成之后就需要去解析属性,那我们就需要先将已经匹配完成的标签进行删除,通过advance(start[0].length)来进行截取,如下图就说明截取成功了,截取成功之后就可以去匹配属性

6.在匹配属性的过程中,只要不是开始标签的结束就可以一直进行匹配,所以可以通过while,如果不是开始标签的结束就一直匹配下去,并且将每次拿到的属性进行保存,每次匹配完在将其截掉,最后还有一个>符号,所以在匹配结束以后end可能就会有值,所以再把end删掉

<img src=“https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/23912023e1eb43fe85c6dec78acfe6dc~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image?) 9. 整个过程就是遇到开始标签处理开始标签,遇到文本处理文本,遇到结束标签处理结束标签,当最后循环能够终止输出为空,就说明处理成功了 9. 现在还没有替换文本只是把字符串删掉了,那就还需要几个方法将它们暴露出去,也可以用htmlparser2去解析htm” style=“margin: auto” />

1.知道方法之后,就开始定义元素类型ELEMENT_TYPE、文本类型TEXT_TYPE,栈stack,指针currentParent,根节点root
2.当遇到开始标签的时候就创建一个AST元素,用createdASTElement方法,然后还需要去判断产生的节点是否是根节点root,如果没有根节点这个节点就是根节点。然后将这个节点放进栈中,同时还需要当前这个指针指向栈中的最后一个。如果currentParent有值就需要让当前节点的父亲等于currentParent,并将node值赋给currentParent.children
3.当遇到文本的时候,这个文本就是当前元素的孩子,直接找到currentParent.children放进去
4.当遇到结束标签的时候,直接弹出栈中最后一个,然后更新currentParent指针,在end中也可以进行校验标签是否合法。

这个时候输出发现children是空的,可以在开始的时候chars执行的时候将空格去掉text = text.replace(/\s/g,'')

最终

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值