概述
在vue模板编译的整个过程,我们可知如下
template字符串生成抽象语法树ast,ast通过渲染函数生成虚拟节点,最后才生成真实UI。本篇主要是关于template转换生成抽象语法树,关于后续过程可移步虚拟DOM和DIFF算法
实现
设置移动指针,判断剩余字符串是开始标签还是结束标签或文字,通过两个栈来保存标签名与容器,遇到开始标签则标签入栈1,生成容器入栈2,遇到文字填充栈2顶内容,闭合则将栈2顶内容移入栈顶前一容器。
parse.js
import parseAttrString from "./parseAttrString";
export default function parse(templateStr) {
let len = templateStr.length;
let index = 0; //指针
let rest = ''; //剩余字符串
let startTagExp = /^\<([a-z]+[1-6]?)(\s[^\<]+)?\>/; //开始标签的正则
let endTagExp = /^\<\/([a-z]+[1-6]?)\>/; //结束标签的正则
let wordExp = /^([^\<]+)\<\/[a-z]+[1-6]?\>/; //文字的正则
let stack1 = [];
let stack2 = [{
'children': []
}];
while (index < len) {
rest = templateStr.substring(index);
if (startTagExp.test(rest)) {
//进入开始标签
let tag = rest.match(startTagExp)[1] //标签名
let attrs = rest.match(startTagExp)[2] //属性
console.log('开始标签:' + tag);
stack1.push(tag)
stack2.push({
'tag': tag,
'attrs': attrs ? parseAttrString(attrs) : undefined,
'children': []
})
//index加值为tag的长度加上<>的长度2
index += tag.length + (attrs != undefined ? attrs.length : 0) + 2;
} else if (endTagExp.test(rest)) {
//进入结束标签
let tag = rest.match(endTagExp)[1]
console.log('结束标签:' + tag);
let popTag = stack1.pop()
let popArr = stack2.pop();
if (tag === popTag) {
stack2[stack2.length - 1].children.push(popArr)
} else {
throw new Error(popTag + '标签没有闭合')
}
index += tag.length + 3;
} else if (wordExp.test(rest)) {
//进入文字内容
let word = rest.match(wordExp)[1]
if (!/^\s+$/.test(word)) {
console.log('文字:' + word);
stack2[stack2.length - 1].children.push({
'text': word,
'type': 3
})
}
index += word.length;
} else {
index++;
}
}
return stack2[0].children[0]
}
对于attrs属性,则去除两侧空格,然后同样通过指针做判断依次截取各项属性的内容,以对象形式返回键值
parseAttrString.js
export default function (attrString) {
//'class="title a b" id="myTitle"'
let attrsArr = [];
let attrsObj = {};
console.log(attrString);
attrString = attrString.trim();
let point = 0;
let isYinHao = false; //是否在引号内
for (let i = 0; i < attrString.length; i++) {
let v = attrString[i];
if (v === '"') {
isYinHao = !isYinHao;
}
if (v === ' ' && !isYinHao) {
if (attrString[point] != ' ') {
attrsArr.push(attrString.substring(point, i))
}
point = i + 1;
}
}
attrsArr.push(attrString.substring(point))
attrsArr.map(item => {
let arrItem = item.split('=')
// console.log(item, key, value);
attrsObj[arrItem[0]] = arrItem[1].match(/^"(.+)"$/)[1]
})
return attrsObj
}