目录
一、概念介绍:
二、抽象语法树与虚拟DOM节点的关系:
三、尝试手写AST语法树:
1. 识别开始结束标签(parse.js):
2. 使用栈形成AST(完善parse.js):
3.识别attrs:
parse.js:
parseAttrsString.js:
在开发Vue的时候编译器会将模板语法编译成正常的HTML语法,而直接编译的时候是非常困难的,因此此时会借助AST抽象语法树进行周转,进而变为正常的HTML语法,使编译工作变得更加简单。
抽象语法树的本质上是一个JS对象,Vue在审视所有HTML结构时是以字符串的新式进行的,最终将其解析为JS对象。AST抽象语法树服务于模板编译,将一种语法翻译为另一种语法。在Vue中将模板语法编译为HTML语法,自己作为中转站。
图示:
抽象语法树的终点是渲染函数(h函数)。
渲染函数(h函数),它既是AST的产物,也是vnode(虚拟节点)的起源。h函数里面是不含指令的。
抽象语法树不会进行diff算法的并且抽象语法树不会直接生成虚拟节点,抽象语法树最终生成的是渲染函数的
注:项目实现环境
webpack@5,webpack-cli@3,webpack-dev-server@3
基本思想:使用指针和栈的思想,使用正则表达式来匹配标签。
实现思路:
当识别到一个开始的标签符号,那么就将这个标签入栈1,把空字符串入栈2。
当识别到的字符是标签内的文字,那么就将栈2栈顶这项改为文字。
当识别到结束标签,那么就将这个标签符号弹栈,就把栈2栈顶的元素push到栈二新的栈顶上的。
export default function(templateString){
//准备指针
var index = 0;
//剩余部分
var rest = '';
//开始标记
var startRegExp = /^\<([a-z]+[1-6]?)\>/;
//结束标签
var endRegExp = /^\<\/([a-z]+[1-6]?)\>/;
//识别文字
var wordRegExp = /^([^\<]+)\<\/([a-z]+[1-6]?)\>/;
//准备两个栈
var stack1 = [];
var stack2= [];
//遍历
while(index也占两位
index += tag.length+2;
}else if(endRegExp.test(rest)){
//识别这个字符是不是一个结束标签
//指针移动标签的长度加3,>占三位
let tag = rest.match(endRegExp)[1];
console.log("检测到结束标记",tag);
//此时tag一定是和栈1顶部的是相同的
if(tag == stack1[stack1.length-1]){
// 弹栈
stack1.pop();
}else{
throw new Error(stack1[stack1.length-1]+"标签没有封闭");
}
index+=tag.length+3;
console.log(stack1);
console.log(stack2);
}else if(wordRegExp.test(rest)){
// 遍历到这个字符是不是文字
let word = rest.match(wordRegExp)[1];
if(!/^\s+$/.test(word)){
// 不是全是空
console.log("检测到文字",word);
}
index+=word.length;
} else{
index++;
// 标签中的文字
}
}
return templateString;
}
当识别到一个开始的标签符号,那么就将这个标签入栈1,把空字符串入栈2。
当识别到的字符是标签内的文字,那么就将栈2栈顶这项改为文字。
当识别到结束标签,那么就将这个标签符号弹栈,就把栈2栈顶的元素push到栈二新的栈顶上的。
export default function(templateString){
//准备指针
var index = 0;
//剩余部分
var rest = '';
//开始标记
var startRegExp = /^\<([a-z]+[1-6]?)\>/;
//结束标签
var endRegExp = /^\<\/([a-z]+[1-6]?)\>/;
//识别文字
var wordRegExp = /^([^\<]+)\<\/([a-z]+[1-6]?)\>/;
//准备两个栈
var stack1 = [];
var stack2= [{'children':[]}];
//遍历
while(index也占两位
index += tag.length+2;
}else if(endRegExp.test(rest)){
//识别这个字符是不是一个结束标签
//指针移动标签的长度加3,>占三位
let tag = rest.match(endRegExp)[1];
console.log("检测到结束标记",tag);
//此时tag一定是和栈1顶部的是相同的
let pop_tag = stack1.pop();
if(tag == pop_tag){
// 弹栈
let pop_arr = stack2.pop();
console.log(pop_arr);
if(stack2.length>0){
stack2[stack2.length-1].children.push(pop_arr);
}
}else{
throw new Error(stack1[stack1.length-1]+"标签没有封闭");
}
index+=tag.length+3;
console.log(stack1);
console.log(stack2);
}else if(wordRegExp.test(rest)){
// 遍历到这个字符是不是文字
let word = rest.match(wordRegExp)[1];
if(!/^\s+$/.test(word)){
// 不是全是空
console.log("检测到文字",word);
// 改变此时Stack2栈顶元素中
stack2[stack2.length-1].children.push({'text':word,'type':3});
}
index+=word.length;
} else{
index++;
// 标签中的文字
}
}
// 此时stack2就是我们之前默认放置的一项了,此时要返回这一项的children即可
return stack2[0].children[0];
}
import parseAttrsString from './parseAttrsString.js'
export default function(templateString){
//准备指针
var index = 0;
//剩余部分
var rest = '';
//开始标记
var startRegExp = /^\<([a-z]+[1-6]?)(\s[^\<]+)?\>/;
//结束标签
var endRegExp = /^\<\/([a-z]+[1-6]?)\>/;
//识别文字
var wordRegExp = /^([^\<]+)\<\/([a-z]+[1-6]?)\>/;
//准备两个栈
var stack1 = [];
var stack2= [{'children':[]}];
//遍历
while(index也占两位
index += tag.length+2+attrsStringLength;
}else if(endRegExp.test(rest)){
//识别这个字符是不是一个结束标签
//指针移动标签的长度加3,>占三位
let tag = rest.match(endRegExp)[1];
console.log("检测到结束标记",tag);
//此时tag一定是和栈1顶部的是相同的
let pop_tag = stack1.pop();
if(tag == pop_tag){
// 弹栈
let pop_arr = stack2.pop();
console.log(pop_arr);
if(stack2.length>0){
stack2[stack2.length-1].children.push(pop_arr);
}
}else{
throw new Error(stack1[stack1.length-1]+"标签没有封闭");
}
index+=tag.length+3;
console.log(stack1);
console.log(stack2);
}else if(wordRegExp.test(rest)){
// 遍历到这个字符是不是文字
let word = rest.match(wordRegExp)[1];
if(!/^\s+$/.test(word)){
// 不是全是空
console.log("检测到文字",word);
// 改变此时Stack2栈顶元素中
stack2[stack2.length-1].children.push({'text':word,'type':3});
}
index+=word.length;
} else{
index++;
// 标签中的文字
}
}
// 此时stack2就是我们之前默认放置的一项了,此时要返回这一项的children即可
return stack2[0].children[0];
}
函数功能:将获取到的字符串attrs转换成数组对象的形式。
思路:利用引号和空格将其拆分。
- 首先遍历字符串,遇到引号后将其 isStr 属性设置成true,此时在引号内遇到空格不用管,当遇到下一个引号时设置 isStr 为false。此时在 isStr为false的情况下再遇见引号就将前面的字符串截取出来放入到数组中。
- 最后将数组里面的内容利用map进行拆分。
//把attrsString变为数组返回
export default function(attrsString){
if(attrsString == undefined) return [];
// 当前是否在引号内
var isStr = false;
// 断点
var point = 0;
// 结果数组
var result = [];
// 遍历attrsString
for(let i = 0;i{
// 根据等号拆分
const o = item.match(/^(.+)="(.+)$/);
return{
name:o[1],
name:o[2]
}
});
return result;
}
注:学习资料《尚硅谷Vue源码系列课程》
代码地址。