Skip to content

模板解析 ​

本节目标:理解“模板解析”的核心思路,并能把它用于实际开发或面试表达。

说明:原 OneNote 中有图片引用,但图片未包含在导出目录中;本页保留了可用的文字与代码内容。 思路:

  • 先将html模板解析成ast语法树;ast语法树的主要属性:
    • Tag
    • Type
    • Attrs
    • Parent
    • children
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.net
ast是抽象语法树,可以描述任意语言,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树;

本站总访问 — 次