VUE2.0 模板编译原理:解析器
Posted 登楼痕
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VUE2.0 模板编译原理:解析器相关的知识,希望对你有一定的参考价值。
模板--[输入]-->模板编译--[输出]-->渲染函数
一、解析器
AST与vnode类似,都是通过js对象来表示节点,比如parent记录父节点,type表示节点类型,children记录子节点这样,所以AST就是一个用js对象描述的节点树。
解析器又分成过滤器解析器、文本解析器(解析类似Hello {{ name }}这种带变量的文本)和HTML解析器(核心,解析模板),这些解析器通过主线被组装在一起。
1、解析器内部运行原理:
主要的就是HTML解析器,在解析的过程中触发各种钩子函数:
-
开始标签钩子函数start(tag:标签名, attrs:属性, unary:是否是自闭合标签)
-
结束标签钩子函数end()
-
文本钩子函数chars(text)
-
注释钩子函数comment(text)
比如:
<div><p>Hello</p></div>
从前向后解析依次就会触发start, start, chars, end, end。
但是AST是有层级关系的,所以需要维护一个栈来记录这层关系,即DOM的深度。因为是从前往后解析,遇到节点就压入栈,所以栈的最里面是根节点,最外面就是当前正在构建的节点的父节点,因此栈也可以用来检测HTML标签是否正确闭合,只要当读取结束标签时,发现栈顶却是另外的标签,那么就警告。在解析过程中不断判断模板的开始标签、文本标签、结束标签、注释从而进行出栈入栈、触发相应的钩子函数和截掉已解析模板,最后知道模板空并且栈为空,说明解析结束得到最终AST。
所以AST的构建和HTML解析器里各个钩子函数密切相关,接下来看看具体的HTML解析器的运行原理:
2、HTML解析器运行原理:
说白了就是对HTML模板字符串进行循环,每次循环截取一段字符串,触发相应钩子函数,重复这个过程,直到剩下的模板为空。
parseHTML(html, options)函数定义在src/compiler/parser/html-parse.js中。
整个解析逻辑包在while循环里,while循环结束,表示解析结束。每次被截取到的片段有很多种,比如开始标签,结束标签,HTML注释(<!-- xxx -->),DOCTYPE,文本,条件注释。
怎么识别是开始标签这些东西呢?当然上正则啦。
对于开始标签:
const ncname = `[a-zA-Z_][\\\\-\\\\.0-9_a-zA-Z${unicodeRegExp.source}]*`
const qnameCapture = `((?:${ncname}\\\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
例如<div class="box"></div>这段来match(startTagOpen),得到的数组是["<div", "div", index: 0, input: "<div class="box"></div>"]。所以只能拿到tag的信息,对于标签的属性和是否自闭合,还需要进一步解析。
(1)属性:拿到开始标签后,就会被截走,于是剩下'class="box"></div>',靠正则去解析属性每次只能识别出一个,如果是多个属性存在,也是需要用while循环来一点点解析的。然后将每次解析到的属性push到attrs数组中去。
(2)自闭合标识:例如<input type="text" />这种,通过startTagClose正则得出,如果是自闭合标签,那么unarySlash就是“/”,否则就是空字串。来用于判断该标签是否需要放入栈中(毕竟自闭合标签没有子节点不需要入栈)。
其他的截取操作其实都是大同小异。那么有个问题,在文本截取中,如果<本身就是文本的一部分呢?
比如剩下的模板字符串是“1<2</div>”这样,如果只是简单判断<前面是文本,那么只能得到1。
思路就是查看1被截取后,剩余的模板中“<2”是否符合前面那些标签的特征,如果不符合,就说明这个<是文本的一部分。之后就把这个<和下一个<之间的字符串截出来加到1上,之后重复判断后面的模板直到文本解析完。
还有一点要注意的就是纯文本内容元素的处理,也就是script、style和textarea三种元素,这个在代码中是有别与之前正常元素的处理逻辑,纯文本内容元素都会被视作文本处理,也就是把这些文本截取出来然后触发chars函数。
3、文本解析器chars():
文本解析器是对HTML解析器解析出来的文本进行二次加工,因为文本分为两类:纯文本、带变量的文本( 例如:Hello {{name}})。所以这种带变量的文本在虚拟DOM渲染的时候,需要将变量替换为真实值。
大致思路:
带不带变量文本的判断通过parseText()函数得出,如果该函数有返回结果,则构建一个带变量的文本类AST并push到父节点的children里,否则就是push一个普通文本节点。
最后Hello {{name}} 将会被解析为'"Hello" +_s(name)'。
内部实现原理:
-
首先使用正则判断是否带变量,也就是是否含有{{xxx}}这样的语法,/\\{\\{((?:.|\\r?\\n)+?)\\}\\}/g,如果是纯文本就直接return;
-
如果有变量,则先把变量左边的文本添加到数组中,然后把当前变量变成_s(x)的形式加入数组;
-
重复步骤2直到所有变量都添加了;
-
如果最后一个变量后面还有文本,则添加到数组中。
4、整体流程总结:
最后自己总结着画了一张流程图
——以上内容学习自《深入浅出Vue.js》
以上是关于VUE2.0 模板编译原理:解析器的主要内容,如果未能解决你的问题,请参考以下文章