Appearance
模板解析
本节目标:理解“模板解析”的核心思路,并能把它用于实际开发或面试表达。
说明:原 OneNote 中有图片引用,但图片未包含在导出目录中;本页保留了可用的文字与代码内容。 思路:
- 先将
html模板解析成ast语法树;ast语法树的主要属性:TagTypeAttrsParentchildren
function createASTElement(tag,attrs,parent = null){
return {
tag,
type:1, //元素
children:[],
parent,
attrs
}
}- 再将
ast语法树生成代码;是一个字符串,里面包含了c(),v(),s()等函数的调用; - 再使用
new Function将代码转换成code函数;
import { generate } from "./generate";
import { parserHTML } from "./parser";export function compileToFunction(template) {
// 1.将模板变成ast语法树
let ast = parserHTML(template);
// 2.代码优化 标记静态节点
// 3.代码生成
let code = generate(ast);
let render = new Function(`with(this){return ${code}}`);
return render;
}模板解析资源:
插件:htmlParser htmlParser2
网站:astexplorer.netast是抽象语法树,可以描述任意语言,js,css,html等正则知识在[]中的特殊字符:^排除-表示范围&&表示与[]可以嵌套其中,需要转序的字符有:-:要使用\-,因为-在[]中是表示范围的,但如果-不在两个字母或数字之间,或者不在\uxxxx表示的字符之间,则可以不转义直接使用;
.:要使用\\.或者直接使用;]:要转序为\\]才能表示]本身;\:\在[]中还是有转义作用的,所以要表示本身,则要使用\\
在这里,由于我们直接是以字符串写正则,然后使用new RegExp()来创建正则,所以字符串中的-和.前加了\进行转译;
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
// aa:aa-xxx
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
// 此正则可以匹配到标签名 匹配到结果的第一个(索引第一个) [1]
const startTagOpen = new RegExp(`^<${qnameCapture}`);
// 匹配标签结尾的 </div> [1]
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
// 匹配属性的
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// [1]属性的key [3] || [4] ||[5] 属性的值 a=1 a='1' a=""
const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 /> >
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // {{ xxx }}html模板解析思路:边解析,边删除已解析内容;查看标签开始标记<在字符串中的位置;如果<在模板内容首位,表示接下来解析的内容是html标签,将会将此html标签的开始标签全部解析完毕(碰到第一个>为准);
在解析开始标签时,根据html标签的特点,开始标签的重要信息有:
标签类型;标签属性:包括类,样式,自定义属性等,
解析完一个开始标签后,我们会删掉已解析的部分,然后continue,即继续进行<位置计算,根据<位置来确定接下来是html标签还是文本内容;如果是html标签,继续解析该html元素的开始标签;如果是文本,则执行文本解析;
如果<在模板内容中的位置大于1,则表示<标签前的内容是文本(因为其它html元素都是<和>包裹的,如果有其它html元素,<的位置肯定为首位,则需要解析该html元素)
解析开始标签思路:用正则去匹配模板字符串;如果匹配上了,我们做两件事,一是解析出标签类型,二是解析出属性;
为此,我们定义了一个变量match,它有tagName和attrs:[]两个属性;从正则匹配的结果中,可以从match的第1个子捕获中,我们获得标签类型;
而标签属性,我们要在接下来的逻辑中进行填充;捕获属性:如果接下来的模板字符不匹配结束字符endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`),但是能匹配属性正则,则将匹配的属性放到match.attrs数组中;匹配一次,删除相应的模板字符串,然后再匹配下一个属性,直到属性匹配为,即模板开始匹配结束字符串或不匹配属性字符串为止;
属性解析完之后,解析结束标签,结束标签的解析就是删除html模板中匹配到的字符串;
最后,模板字符串删除了开始标签的全部;并且返回了一个match;
接下来,我们处理一下match,将它利用栈构造成一个ast树;