Vue2源码共读-真实节点转虚拟节点

176 阅读2分钟

上篇文章我们已经把标签都解析好了,这里我们需要将标签进行重组,将解析后的结果变成一个树结构。这里我们用的一个非常主要的数据结构,叫做栈。当遇到开头标签的时候将标签给他塞进去。然后碰到闭合标签,我们就将栈中的标签给他弹出去。

更具上面这个思想,我们就可以将一个平面的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)
}

image.png

好的,现在我们构造好了这个树结构,我们就开始生成抽象语法树AST了

  • _c(): 标签
  • _v(): 文本
  • _s(): 马斯塔器
  1. 在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语法树的

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!