上篇文章我们已经把标签都解析好了,这里我们需要将标签进行重组,将解析后的结果变成一个树结构。这里我们用的一个非常主要的数据结构,叫做栈。当遇到开头标签的时候将标签给他塞进去。然后碰到闭合标签,我们就将栈中的标签给他弹出去。
更具上面这个思想,我们就可以将一个平面的HTML标签里面的嵌套关系变成抽象化的树结构
function createAstElement(tagName,attrs){
return {
tag:tagName,
type: 1,
children: [],
parent: null,
attrs
}
}
当我们遇到开始标签的时候,我们就可以开始解析了。
- type:1 标签
- type:2 内容
let stack=[]
let root=null
function start(tagName,attrbutes){
let parent=stack[stack.length-1]
let element=createAstElement(tagName,attributes)
if(!root){
root=element
}
element.parent=parent // 当放入栈中时。记录父亲是谁
if(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
})
}
}
好的,这里我们已经将标签名解析成一个树结构了。我们现在来观察下这个树是不是和我们想的一样
export function compileToFunction(template){
parserHTML(template)
console.log(root)
}
好的,现在我们构造好了这个树结构,我们就开始生成抽象语法树AST了
- _c(): 标签
- _v(): 文本
- _s(): 马斯塔器
- 在compile库下面新建一个文件
generator.js, 然后compile库的index.js中引入它的方法
// index.js
export function compileToFunction(template){
parserHTML(template)
let code=generate(root)
}
function getProps(attrs){ //[{name:'xxx',value:'xxx'},{name:'xxx',value:'xxx'}]
let str=''
for(let i=0;i<attrs.length;i++){
let attr=attrs[i]
if(attr.name=='style'){
let styleObj={}
attr.value.replace( /([^;:]+):([^;:]+)/g,function(){
styleObj[arguments[1]]=arguments[2]
})
attr.value=styleObj
}
str+=`${attr.name}:${JSON.stringify(attr.value)}`
}
return `{${str.slice(0,-1)}}`
}
function gen(el){
if(el.type==1){
return generate(el)
}else{
let text=el.text
return `_v(${text})`;
}
}
function genChildren(el){
let children=el.chilren;
if(children){
return children.map(c=>gen(c)).join(',')
}
}
export function generate(root){ //_c('div',{id:'app',a:1},_c('span',{},'world'),_v('hello'))
// 遍历这个树,然后将树拼接成字符串
let children=genChildren(el)
let code=`_c('${el.tag}',${el.attrs.length?getProps(el.attrs):undefined}),${
children?`,${children}`:''
})`
return code
}
上述代码可以解决正常的标签解析,但是Vue一直都需要考虑一种特殊情况,那就是马斯塔奇语法,如果说我们文本里面嵌套的是111{{aaa}}222,我们上面的代码还是会将其解析为_v('111{{aaa}}222').这样就无法实现我们的目的了。因此我们需要对上面的源码做分割
匹配马斯塔奇的正则: const defaultTagRE=/\{\{((?:.|\r?\n)+?)\}\}/g
function gen(el){
if(el.type==1){
return generate(el)
}else{
let text=el.text
if(!defaultTagRE.test(text)){
return `_v(${text})`;
}else{
// 进行拆分
let tokens=[] // hello {{arr}} world
let match;
let lastIndex=defaultTagRE.lastIndex=0
while(match=defaultRE.exec(text)){ // 看有没有匹配到
let index=match.index; // 开始索引
if(index>lastIndex){
tokens.push(JSON.stringify(text.slice(lastIndex,index)))
}
tokens.push(`_s(${match[1].trim()})`) // JSON.stringfy()
lastIndex=index+match[0].length
}
if(lastIndex<text.length){
tokens.push(JSON.stringify(text.slice(lastIndex)) )
}
return `_v(${tokens.join('+')})`
}
}
}
好的,现在我们的字符串结构就已经声明好了,下篇文章我们将会介绍_v,_c,_s是怎么将特定的内容解析成AST语法树的
我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!