• Vue源码学习(三):<templete>渲染第二步,创建ast语法树


    好家伙,书接上回

     

    在上一篇Vue源码学习(二):渲染第一步,模板解析中,我们完成了模板解析

    现在我们继续,将模板解析的转换为ast语法树

     

    1.前情提要

    代码已开源https://github.com/Fattiger4399/analytic-vue.git手动调试一遍,

    胜过我解释给你听一万遍

    复制代码
    function start(tag, attrs) { //开始标签
        console.log(tag, attrs, '开始的标签')
    }
    
    function charts(text) { //获取文本
        console.log(text, '文本')
    }
    
    function end(tag) { //结束的标签
        console.log(tag, '结束标签')
    }
    复制代码

    在这里,我们知道start,charts,end分别可以拿到

    我们的`开始标签`,`文本`,`结束标签`

    效果如下:(仔细看,这也是我们实验要用到的例子)

     

    随后我们开始改造这几个方法

     

    2.代码详解

    2.1.ast树节点的结构

    确定我们ast树节点的结构:

    复制代码
    let root; //根元素
    let createParent //当前元素的父亲
    let stack = [] 
    function createASTElement(tag, attrs) {
        return {
            tag,
            attrs,
            children: [],
            type: 1,
            parent: null
        }
    }
    复制代码

    节点元素分别为

    • tag:标签名
    • attrs:标签属性
    • children:子元素(数组)
    • type:类型(后面会用到,目前"1"代表标签"3"代表文本)
    • parent:父元素

     

    2.2.start()方法

    复制代码
    function start(tag, attrs) { //开始标签
        let element = createASTElement(tag, attrs) //生成一个开始标签元素
        //查看root根元素是否为空
        //若是,将该元素作为根
        //非原则
        if (!root) {
            root = element
        }
        createParent = element
        stack.push(element)
        console.log(tag, attrs, '开始的标签')
    }
    复制代码

    此处,生成一个开始标签元素,判断root是否为空,若为空,则将该元素作为根元素

    随后将该元素作为父元素.

     

    2.3.charts()方法

    复制代码
    function charts(text) { //获取文本
        console.log(text, '文本')
        // text = text.replace(/a/g,'')
        if(text){
            createParent.children.push({
                type:3,
                text
            })
        }
        // console.log(stack,'stack')
    }
    复制代码

    这个好理解,将"文本内容"作为父元素的孩子

     

    2.4.end()方法

    复制代码
    function end(tag) { //结束的标签
        let element = stack.pop()
        createParent = stack[stack.length - 1]
        if (createParent) { //元素闭合
            element.parent = createParent.tag
            createParent.children.push(element)
        }
        console.log(tag, '结束标签')
    }
    复制代码

    此处,我们先将栈stack最新的元素弹出栈(作为当前元素,我们要对他进行操作),

    随后获取栈的前一个元素作为父元素,

    当前元素的父元素属性指向父元素的标签属性

    随后将该元素推入父元素的children中,

    emmmm,我还是说人话吧

     

    假设现在stack=['div','h1']

    然后pop了,createParent = 'h1'

    'h1'.parent =>'div'

    'div'.children =>'h1'

    (多看几遍就理解了,其实非常简单)

     

    来看看最终实现的ast语法树长什么样子

     (父子关系和谐)

     

    搞定啦!

     

    3.完整代码

    复制代码
    const attribute =
        /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
    //属性 例如:  {id=app}
    const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; //标签名称
    const qnameCapture = `((?:${ncname}\\:)?${ncname})` //
    const startTagOpen = new RegExp(`^<${qnameCapture}`) //标签开头
    const startTagClose = /^\s*(\/?)>/ //匹配结束标签 的 >
    const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) //结束标签 例如
    const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g let root; //根元素 let createParent //当前元素的父亲 let stack = [] function createASTElement(tag, attrs) { return { tag, attrs, children: [], type: 1, parent: null } } function start(tag, attrs) { //开始标签 let element = createASTElement(tag, attrs) //生成一个开始标签元素 //查看root根元素是否为空 //若是,将该元素作为根 //非原则 if (!root) { root = element } createParent = element stack.push(element) console.log(tag, attrs, '开始的标签') } function charts(text) { //获取文本 console.log(text, '文本') // text = text.replace(/a/g,'') if(text){ createParent.children.push({ type:3, text }) } // console.log(stack,'stack') } function end(tag) { //结束的标签 let element = stack.pop() createParent = stack[stack.length - 1] if (createParent) { //元素闭合 element.parent = createParent.tag createParent.children.push(element) } console.log(tag, '结束标签') } export function parseHTML(html) { while (html) { //html 为空时,结束 //判断标签 <> let textEnd = html.indexOf('<') //0 // console.log(html,textEnd,'this is textEnd') if (textEnd === 0) { //标签 // (1) 开始标签 const startTagMatch = parseStartTag() //开始标签的内容{} if (startTagMatch) { start(startTagMatch.tagName, startTagMatch.attrs); continue; } // console.log(endTagMatch, '结束标签') //结束标签 let endTagMatch = html.match(endTag) if (endTagMatch) { advance(endTagMatch[0].length) end(endTagMatch[1]) continue; } } let text //文本 if (textEnd > 0) { // console.log(textEnd) //获取文本内容 text = html.substring(0, textEnd) // console.log(text) } if (text) { advance(text.length) charts(text) // console.log(html) } } function parseStartTag() { // const start = html.match(startTagOpen) // 1结果 2false // console.log(start,'this is start') // match() 方法检索字符串与正则表达式进行匹配的结果 // console.log(start) //创建ast 语法树 if (start) { let match = { tagName: start[1], attrs: [] } // console.log(match,'match match') //删除 开始标签 advance(start[0].length) //属性 //注意 多个 遍历 //注意> let attr //属性 let end //结束标签 //attr=html.match(attribute)用于匹配 //非结束位'>',且有属性存在 while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { // console.log(attr,'attr attr'); //{} // console.log(end,'end end') match.attrs.push({ name: attr[1], value: attr[3] || attr[4] || attr[5] }) advance(attr[0].length) //匹配完后,就进行删除操作 } //end里面有东西了(只能是有">"),那么将其删除 if (end) { // console.log(end) advance(end[0].length) return match } } } function advance(n) { // console.log(html) // console.log(n) html = html.substring(n) // substring() 方法返回一个字符串在开始索引到结束索引之间的一个子集, // 或从开始索引直到字符串的末尾的一个子集。 // console.log(html) } // console.log(root) return root }
    复制代码

     

  • 相关阅读:
    游戏平台采集数据
    docker搭建waline评论系统
    C++(第二篇):C++的类和对象(上)
    C#10新特性-lambda 表达式和方法组的改进
    Java面试题(JVM篇)
    虚拟机CentOS和windows 通过共享文件夹互传
    Altium Dsigner 20 工艺参数设置修改
    PyQt5安装详细教程
    谈谈数字化转型的几个关键问题
    Spring Cloud Gateway夺命连环10问,带你彻底了解gateway
  • 原文地址:https://www.cnblogs.com/FatTiger4399/p/17683332.html