先看效果 展示如下:
- HTML模版
- 转成ast语法树后
在学习之前,我们需要了解这么一个问题,为什么要将HTML字符串解析成对应的 AST语法树。
为什么?
-
语法分析:HTML字符串是一种标记语言,其中包含了大量的标签、属性、文本等内容。通过解析HTML字符串,可以将其转换为更易于操作和理解的数据结构,方便对HTML进行进一步处理。
-
数据驱动:现代前端开发中,数据驱动视图渲染是一种常见的模式。在许多框架中,开发者可以使用类似JSX或模板语言的方式编写组件的结构,然后通过解析和转换成AST树来实现动态渲染。
-
模板编译:许多前端框架(如Vue、React等)都会将模板编译成渲染函数,以实现高效的视图更新。在这个过程中,将模板解析成AST树是必不可少的步骤。
-
性能优化:通过将HTML字符串转换成AST树,可以更方便地进行性能优化,比如进行静态节点的标记、事件绑定等操作,以提高页面渲染的效率。
-
安全性:通过AST树可以更容易地进行XSS(跨站脚本攻击)防护,对用户输入的HTML进行合理的过滤和转义,避免恶意脚本的注入。
优势:
-
更高效的处理和操作:AST提供了对HTML结构的抽象表示,使得可以更轻松地对HTML进行遍历、操作和分析。这使得在前端开发中,可以更方便地实现诸如模板编译、组件化等功能。
-
提高性能:在前端框架中,将HTML模板转换为AST可以帮助进行模板编译,将模板转换为可执行的渲染函数。这样可以避免在每次渲染时重新解析模板,从而提高渲染性能。
-
支持差异化更新:通过比较两个AST树,可以更高效地实现差异化更新,即只更新发生变化的部分,而不需要重新渲染整个页面。这对于提高页面渲染性能和用户体验至关重要。
-
便于优化和分析:AST树可以用于静态分析,有助于发现潜在的性能问题或安全问题,以进行优化和安全防护。
-
支持扩展和定制:通过AST树,可以轻松实现对HTML的自定义扩展,如自定义指令、自定义组件等功能,从而提供更灵活的开发和定制能力。
案例:可以动手敲一敲
直接新建一个html文件夹,将一下的代码放进script先运行一下,体验一下,然后再去理解,如果能手写的话,手写一遍。
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // 标签名
const qnameCapture = `((?:${ncname}\\:)?${ncname})`; // 用来获取的标签名的 match 后的索引为1的
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 匹配开始标签的
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配闭合标签的
// 匹配属性的正则表达式
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
const startTagClose = /^\s*(\/?)>/; // 匹配开始标签的闭合部分
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // 匹配双花括号内容的正则表达式,用于处理文本节点中的插值表达式// 创建 AST 元素
function createAstElement(tagName, attrs) {return {tag: tagName,type: 1, // 代表元素节点children: [],parent: null,attrs}
}let root = null;
let stack = [];// 处理开始标签
function start(tagName, attributes) {let parent = stack[stack.length - 1];let element = createAstElement(tagName, attributes);if (!root) {root = element;}if (parent) {element.parent = parent;parent.children.push(element);}stack.push(element);
}// 处理结束标签
function end(tagName) {let last = stack.pop();if (last.tag !== tagName) {throw new Error('标签有误');}
}// 处理文本节点
function chars(text) {text = text.replace(/\s/g, ""); // 移除空格let parent = stack[stack.length - 1];if (text) {parent.children.push({type: 3, // 代表文本节点text})}
}// 解析 HTML 字符串,生成对应的 AST
function parserHTML(html) {function advance(len) {html = html.substring(len);}function parseStartTag() {const start = html.match(startTagOpen);if (start) {const match = {tagName: start[1],attrs: []}advance(start[0].length);let end;let attr;while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {match.attrs.push({ name: attr[1], value: attr[3] || attr[4] || attr[5] });advance(attr[0].length);}if (end) {advance(end[0].length);}return match;}return false; // 不是开始标签}// 逐步解析HTML字符串while (html) {let textEnd = html.indexOf('<'); // 当前解析的开头 if (textEnd == 0) {const startTagMatch = parseStartTag(html); // 解析开始标签if (startTagMatch) {start(startTagMatch.tagName, startTagMatch.attrs);continue;}const endTagMatch = html.match(endTag);if (endTagMatch) {end(endTagMatch[1]);advance(endTagMatch[0].length);continue;}}let text;if (textEnd > 0) {text = html.substring(0, textEnd)}if (text) {chars(text);advance(text.length);}}return root;
}let htmls = '<div>111</div>';
let str = parserHTML(htmls);
console.log("str", str);