您现在的位置是:亿华云 > IT科技

Vue2剥丝抽茧-模版编译之生成AST

亿华云2025-10-09 13:03:48【IT科技】1人已围观

简介​AST 结构AST​即抽象语法树,在虚拟dom、eslint、babel​都有接触过了,简单来说就是一种描述dom​的数据结构。通过AST​可以还原dom​,也可以把dom​转为AST。因为是树的结

​AST 结构

AST​ 即抽象语法树,剥版编在 虚拟dom、丝抽生成eslint、茧模babel​ 都有接触过了,剥版编简单来说就是丝抽生成一种描述 dom​ 的数据结构。通过 AST​ 可以还原 dom​ ,茧模也可以把 dom​ 转为 AST 。剥版编

因为是丝抽生成树的结构,所以肯定有一个 children​ 字段来保存子节点,茧模同时有 parent​ 来保存父节点。剥版编其他的丝抽生成话还有 tag​ 名,节点类型,茧模type = 1​ 代表普通节点类型,剥版编type=3​ 表示普通文本类型,丝抽生成type=2 代表有插值的茧模的文本类型。

提供一个函数 createASTElement​ 来生成 dom 节点,后续会用到。

export function createASTElement(tag, parent) {

return {

type: 1,

tag,

parent,

children: [],

};

}栈

因为 dom 是一对一对出现的,一对中可能又包含多对,因此和括号匹配一样,这里就缺少不了栈了。

遇到开始标签就入栈,遇到结束标签就出栈,这样就可以保证栈顶元素始终是后续节点的b2b供应网父节点。

举个例子,对于 <div><span>3<5吗</span><span>?</span></div> 。

1、

div span 3<5吗 /span span ? /span /div

^

stack:[div],当前栈顶 div,后续节点为 div 的子节点

2、

div span 3<5吗 /span span ? /span /div

^

stack:[div, span],当前栈顶 span,后续节点为 span 的子节点

3、

div span 3<5吗 /span span ? /span /div

^

stack:[div, span],当前栈顶 span,3<5吗 属于 span

4、

div span 3<5吗 /span span ? /span /div

^

stack:[div],遇到结束节点 span,栈顶的 span 去掉,后续节点为 div 的子节点

5、

div span 3<5吗 /span span ? /span /div

^

stack:[div, span],当前栈顶 span,后续节点为 span 的子节点

6、

div span 3<5吗 /span span ? /span /div

^

stack:[div, span],当前栈顶 span,? 属于 span

7、

div span 3<5吗 /span span ? /span /div

^

stack:[div],遇到结束节点 span,栈顶的 span 去掉,后续节点为 div 的站群服务器子节点

8、

div span 3<5吗 /span span ? /span /div

^

stack:[],遇到结束节点 div,栈顶的 div 去掉,遍历结束整体算法

添加 stack​ 变量、添加 currentParent​ 保存当前的父节点、添加 root 保存根节点。

let root;

let currentParent;

let stack = [];

接下来完善 模版编译之分词​ 中遗留的 start​ 、end、chars 三个回调函数。

parseHTML(template, {

start: (tagName, unary, start, end) => {

console.log("开始标签:", tagName, unary, start, end);

},

end: (tagName, start, end) => {

console.log("结束标签:", tagName, start, end);

},

chars: (text, start, end) => {

console.log("文本:", text, start, end);

},

});

start 函数

start: (tagName, unary, start, end) => {

let element = createASTElement(tagName, currentParent);

if (!root) {

root = element;

}

if (!unary) {

currentParent = element;

stack.push(element);

} else {

closeElement(element);

}

},

先调用 createASTElement​ 生成一个 AST​ 节点,如果当前是第一个开始节点,就将 root 赋值,接下来判断是否是出一元节点。

如果是一元节点直接调用 closeElement ,将当前节点加入到父节点中。

function closeElement(element) {

if (currentParent) {

currentParent.children.push(element);

element.parent = currentParent;

}

}

end 函数

end: (tagName, start, end) => {

const element = stack[stack.length - 1];

// pop stack

stack.length -= 1;

currentParent = stack[stack.length - 1];

closeElement(element);

},

出栈,更新当前父节点,并且将出栈的元素加入到父节点中。

chars 函数

chars: (text, start, end) => {

if (!currentParent) {

return;

}

const children = currentParent.children;

if (text) {

let child = {

type: 3,

text,

};

children.push(child);

}

},

这里只考虑了 type​ 为 3 的普通文本节点。

整体代码

结合 模版编译之分词 中实现的分词,整体代码如下:

const unicodeRegExp =

/a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;

const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${ unicodeRegExp.source}]*`;

const qnameCapture = `((?:${ ncname}\\:)?${ ncname})`;

const startTagOpen = new RegExp(`^<${ qnameCapture}`);

const startTagClose = /^\s*(\/?)>/;

const endTag = new RegExp(`^<\\/${ qnameCapture}[^>]*>`);

export function createASTElement(tag, parent) {

return {

type: 1,

tag,

parent,

children: [],

};

}

export function parseHTML(html, options) {

let index = 0;

while (html) {

let textEnd = html.indexOf("<");

if (textEnd === 0) {

// Start tag:

const startTagMatch = parseStartTag();

if (startTagMatch) {

handleStartTag(startTagMatch);

continue;

}

// End tag:

var endTagMatch = html.match(endTag);

if (endTagMatch) {

var curIndex = index;

advance(endTagMatch[0].length);

parseEndTag(endTagMatch[1], curIndex, index);

continue;

}

}

let text, rest, next;

if (textEnd >= 0) {

rest = html.slice(textEnd);

while (!endTag.test(rest) && !startTagOpen.test(rest)) {

// < in plain text, be forgiving and treat it as text

next = rest.indexOf("<", 1);

if (next < 0) break;

textEnd += next;

rest = html.slice(textEnd);

}

text = html.substring(0, textEnd);

}

if (textEnd < 0) {

text = html;

}

if (text) {

advance(text.length);

}

if (options.chars && text) {

options.chars(text, index - text.length, index);

}

}

function advance(n) {

index += n;

html = html.substring(n);

}

function parseStartTag() {

const start = html.match(startTagOpen);

if (start) {

const match = {

tagName: start[1],

attrs: [],

start: index,

};

advance(start[0].length);

let end = html.match(startTagClose);

if (end) {

match.unarySlash = end[1];

advance(end[0].length);

match.end = index;

return match;

}

}

}

function handleStartTag(match) {

const tagName = match.tagName;

const unarySlash = match.unarySlash;

const unary = !!unarySlash;

options.start(tagName, unary, match.start, match.end);

}

function parseEndTag(tagName, start, end) {

options.end(tagName, start, end);

}

}

const template = "

3<5吗?";

console.log(template);

function parse(template) {

let root;

let currentParent;

let stack = [];

function closeElement(element) {

if (currentParent) {

currentParent.children.push(element);

element.parent = currentParent;

}

}

parseHTML(template, {

start: (tagName, unary, start, end) => {

let element = createASTElement(tagName, currentParent);

if (!root) {

root = element;

}

if (!unary) {

currentParent = element;

stack.push(element);

} else {

closeElement(element);

}

},

end: (tagName, start, end) => {

const element = stack[stack.length - 1];

// pop stack

stack.length -= 1;

currentParent = stack[stack.length - 1];

closeElement(element);

},

chars: (text, start, end) => {

if (!currentParent) {

return;

}

const children = currentParent.children;

if (text) {

let child = {

type: 3,

text,

};

children.push(child);

}

},

});

return root;

}

const ast = parse(template);

console.log(ast);

输入 <div><span>3<5吗</span><span>?</span></div> 。

最终生成的源码下载 AST 结构如下:

总结

这篇文章实现了最简单情况的 AST​ 生成,了解了整个的结构,下一篇文章会通过 AST​ 生成 render 函数。

很赞哦!(9)