Vue
源码学习
AST抽象语法树
模板语法 ====》 抽象语法树AST ====》 正常的HTML语法
通过抽象语法树进行过渡,让编译工作变得简单
抽象语法树本质上就是一个JS对象
递归算法
//将array变为obj
let array = [1,2,3,[4,5]]
let obj = {
{val:1},
{val:2},
{val:3},
{children:
{val:4},
{val:5}
}
}
function fun(o){
if(typeof o == 'number'){
return {val:o}
}else if(Array.isArray(o)){
return {
children:o.map(item=>fun(item))
}
}
}
console.log(fun(array))
栈数据结构
//将 3[2[a]2[b]] 变为 aabbaabbaabb
function fun(templateStr){
var index = 0
//存数字
var stack1 = [];
//存字符串
var stack2 = [];
//剩余部分
var res = templateStr
while(index<templateStr.length-1){
rest = templateStr.substring(index)
//判断当前是不是以数字开头
if(/^\d+\[/.test(rest)){
let times = Number(rest.match(/^(\d+)\[/))
stack1.push(times)
stack2.push('')
//让指针后移,times这个数字是多少位就后移多少位加一位
index += times.toString().length
}else if(/^\w+]/.test(rest)) {
//如果是字母,那么栈顶这项改为字母
let word = res.match(/^\(w+)]/)[1]
stack2[stack2.length-1] = word
//让指针后移,word这个词语多少位就后移多少位
index += word.length
}else if(rest[0] === ']'){
//如果这个字符是],那么就将stack1弹栈,stack2弹栈,把字符串的新栈顶的元素重复刚刚的这个次数拼接到新栈顶
let times = stack1.pop()
let word = stack2.pop()
stack2[stack2.length - 1] += word.repeat(times)
index++
}
}
//while结束以后,stack1和stack2肯定还剩一项,返回栈2中剩下的一项,重复栈1的这一项次数,组成的这个字符串。如果剩的个数不对,那么就是用户的问题了
return stack2[0].repeat(stack1[0])
}
手写AST函数
入口函数
import parse from './parse.js'
let templateString = `
<div>
<h3>你好</h3>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
</div>
`
const ast = parse(templateString)
定义主函数parse
export default function parse(){
//指针
let index = 0
//剩余部分
let rest = ''
//开始标记
let startRegExp = /^\<([a-z]+[1-6]?)\>/
//结束标记
let endRegExp = /^\<\/([a-z]+[1-6]?)\>/
//抓取结束标记前的文字
let wordRegExp = /^([^\<])\<\/([a-z]+[1-6]?)\>/
//准备两个栈
let stack1 = []
let stack2 = [{'children':[]}]
while (index < templateString.length -1 ){
//console.log(templateString[index])
rest = tempalteString.substring(index)
if(startRegExp.test(rest)){
//如果是开始标签
let tag = rest.match(startRegExp)[1]
//将开始标记推入栈1中
stack1.push(tag)
//将空数组推入栈2中
stack2.push({'tag':tag,'children':[]})
//指针移动标签的长度加2,为什么要加2呢,因为<>也占两个位
index += tag.length + 2
}else if(endRegExp.test(rest)){
//如果是结束标签
let tag = rest.match(endRegExp)[1]
let pop_tag = stack1.pop()
//此时tag一定是和栈1顶相同的
if(tag == pop_tag){
let pop_arr = stack2.pop()
if(stack2.length>0){
stack2[stack2.length - 1].children.push(pop_arr)
}
}else{
throw new Error('标签没有封闭')
}
//指针移动标签的长度加3,为什么要加3呢,因为</>也占3个位
index += tag.length + 3
}else if(wordRegExp.test(rest)){
//如果是文字,也不是全空
let word = rest.match(wordRegExp)[1]
if(!/^\s+$/.test(rest)){
//推入到stack2栈顶元素中
stack2[stack2.length - 1].children.push({'text':word,'type':3})
}
//指针移动标签的长度加3,为什么要加3呢,因为</>也占3个位
index += word.length
}else{
//标签里的文字
index++
}
}
return stack2[0].children
}
此上就完成了ast的最基本功能
下面要解决识别标签内的attrs
修改主函数parse
import parseAttrsString from './parseAttrsString.js'
export default function parse(){
//指针
let index = 0
//剩余部分
let rest = ''
//开始标记
let startRegExp = /^\<([a-z]+[1-6]?)(\s[^\<]+)?\>/
//结束标记
let endRegExp = /^\<\/([a-z]+[1-6]?)\>/
//抓取结束标记前的文字
let wordRegExp = /^([^\<])\<\/([a-z]+[1-6]?)\>/
//准备两个栈
let stack1 = []
let stack2 = [{'children':[]}]
while (index < templateString.length -1 ){
//console.log(templateString[index])
rest = tempalteString.substring(index)
if(startRegExp.test(rest)){
//如果是开始标签
let tag = rest.match(startRegExp)[1]
let attrsString = res.match(startRegExp)[2]
//将开始标记推入栈1中
stack1.push(tag)
//将空数组推入栈2中
stack2.push({'tag':tag,'children':[],'attrs':parseAttrsString(attrsString)})
//指针移动标签的长度加2,为什么要加2呢,因为<>也占两个位
const attrsStringLength = attrsString != null ? attrsString.length : 0
index += tag.length + 2 + attrsStringLength
}else if(endRegExp.test(rest)){
//如果是结束标签
let tag = rest.match(endRegExp)[1]
let pop_tag = stack1.pop()
//此时tag一定是和栈1顶相同的
if(tag == pop_tag){
let pop_arr = stack2.pop()
if(stack2.length>0){
stack2[stack2.length - 1].children.push(pop_arr)
}
}else{
throw new Error('标签没有封闭')
}
//指针移动标签的长度加3,为什么要加3呢,因为</>也占3个位
index += tag.length + 3
}else if(wordRegExp.test(rest)){
//如果是文字,也不是全空
let word = rest.match(wordRegExp)[1]
if(!/^\s+$/.test(rest)){
//推入到stack2栈顶元素中
stack2[stack2.length - 1].children.push({'text':word,'type':3})
}
//指针移动标签的长度加3,为什么要加3呢,因为</>也占3个位
index += word.length
}else{
//标签里的文字
index++
}
}
return stack2[0].children
}
定义一个parseAttrsString.js处理attrs、
//把attrsString变为数组返回
export default function(attrsString){
if(attrsString == undefined) return []
//当前是否在引号内
let isYinhao = false
//断点
let point = 0
//结果数组
let result = []
//遍历attrsString,不能spilt()
for(let i = 0; i<attrsString.length;i++){
let char = attrsString[i]
if(char == '"'){
isYinhao = !isYinhao
}else if(char == ' ' && !isYinhao){
//遇见了空格且不在引号中
result.push(attrsString.substring(point,i))
point = i
}
}
result = result.map(item=>{
const o = item.match(/^(.+)=(.+)$/)
return {name:o[1],value:o[2]}
})
return result
}
ringLength = attrsString != null ? attrsString.length : 0
index += tag.length + 2 + attrsStringLength
}else if(endRegExp.test(rest)){
//如果是结束标签
let tag = rest.match(endRegExp)[1]
let pop_tag = stack1.pop()
//此时tag一定是和栈1顶相同的
if(tag == pop_tag){
let pop_arr = stack2.pop()
if(stack2.length>0){
stack2[stack2.length - 1].children.push(pop_arr)
}
}else{
throw new Error('标签没有封闭')
}
//指针移动标签的长度加3,为什么要加3呢,因为</>也占3个位
index += tag.length + 3
}else if(wordRegExp.test(rest)){
//如果是文字,也不是全空
let word = rest.match(wordRegExp)[1]
if(!/^\s+$/.test(rest)){
//推入到stack2栈顶元素中
stack2[stack2.length - 1].children.push({'text':word,'type':3})
}
//指针移动标签的长度加3,为什么要加3呢,因为</>也占3个位
index += word.length
}else{
//标签里的文字
index++
}
}
return stack2[0].children
}
定义一个parseAttrsString.js处理attrs、
//把attrsString变为数组返回
export default function(attrsString){
if(attrsString == undefined) return []
//当前是否在引号内
let isYinhao = false
//断点
let point = 0
//结果数组
let result = []
//遍历attrsString,不能spilt()
for(let i = 0; i<attrsString.length;i++){
let char = attrsString[i]
if(char == '"'){
isYinhao = !isYinhao
}else if(char == ' ' && !isYinhao){
//遇见了空格且不在引号中
result.push(attrsString.substring(point,i))
point = i
}
}
result = result.map(item=>{
const o = item.match(/^(.+)=(.+)$/)
return {name:o[1],value:o[2]}
})
return result
}