手写vue2学习笔记(二)模板编译,ast语法树

本文介绍了Vue中的模板编译过程,包括如何处理el属性和template属性,以及如何将HTML文本解析成AST语法树,为后续的虚拟DOM渲染做准备。
摘要由CSDN通过智能技术生成

上文中,我们已经实现了,数组的响应式和对象的响应式。接下来会学习到模板编译,ast语法树,虚拟DOM

1. 模板编译

在使用vue时,我们是使用el属性或者vm.$mount()的这两种方式去挂载实例。我们可以在实例内去写一些模板,也可以通过template属性去写一些模板

<div id="app">
    <div> {{name}} </div>
    <span>{{age}}</span>
</div>
const vue = new Vue({
        data() {
            return {
                name: 'aaa',
                age: 18,
                address: {
                    ad :1
                },
                arr:[1,2,3,4,{a:1}]
            }
        },
        el: "#app",
        template: `<div>name</div>`
    })

在了解了这一些之后就可以开始写源码

 在init.js内

如果有options里面有el属性那么就帮他去调Vue的$mount方法

首先,根据el去获取挂载的元素,然后看没有用random属性,没有就去帮他生成,使用compileToFunction(template)方法,以template属性内的内容为主

 // 进行模板编译,挂载实例
 if(options.el) vm.$mount(options.el)
 Vue.prototype.$mount = function (el) {
        const vm = this
        let ops = vm.$options
        el = document.querySelector(el)
        if(!ops.random) {
            let template;
            if(!ops.template && el) template = el.outerHTML
            else {
                if(el) template = ops.template
            }
            if(template) ops.random = compileToFunction(template)
        }
    }

compileToFunction方法,就是将el绑定的根元素的html文本进行解析或者template属性的html文本进行解析,把他变为ast语法树的一种结构。

首先对html文本进行解析,也就是使用正则表达式匹配对html的标签,文本,属性一一的匹配出来,然后匹配完毕之后就进行删除。不断的匹配删除,直到html被匹配完就截至。

可以通过该debugger来观察整个匹配的过程

compile.js内 

// 标签名 a-aaa
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
// 命名空间标签 aa:aa-xxx
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
// 开始标签-捕获标签名 <div
const startTagOpen = new RegExp(`^<${qnameCapture}`);
// 结束标签-匹配标签结尾的 </div>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
// 匹配属性
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// 匹配标签结束的 > <br/>
const startTagClose = /^\s*(\/?)>/;
// 匹配 {{ }} 表达式
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;

function parseHTML(html) {
    function advice(n) {
        html = html.substring(n);
    }
    function parseStarTag() {
        let start = html.match(startTagOpen)
        if(start) {
            let match = {
                tagName: start[1],
                attrs:[]
            }
            advice(start[0].length)
            // 解析属性
            let attr,end;
            while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
                advice(attr[0].length);
                match.attrs.push({name:attr[1],value:attr[3] || attr[4] || attr[5]})
            }
            if(end) advice(end.length)
            return match
        }
        return false
    }
    while(html) {
        debugger
        // textEnd = 0说明是标签的开始或者结束位置,大于0说明是文本的结束位置
        let textEnd = html.indexOf('<')
        if(textEnd == 0) {
            let startTagMatch = parseStarTag()
            if(startTagMatch) {
                continue
            }
            let endTagMatch = html.match(endTag)
            if(endTagMatch) {
                advice(endTagMatch[0].length)
                continue
            }
        }
        if(textEnd > 0) {
            let text = html.substring(0,textEnd)
            if(text) {
                advice(text.length)
            }
        }
    }
}
export function compileToFunction(template) {
    // 1. 将template转化为ast语法树
    let ast = parseHTML(template);
}

2. 生成ast语法树

对匹配到的文本,标签,属性声明三个对应得方法进行处理,对于匹配后得元素父子关系通过栈的形式进行划分。

通过currentParent属性,标识栈内最后一个元素,如果有新元素进来,那么新元素就是之前的最后一个元素的儿子,currentParent属性将会更新,指向新进来的元素,直到将找到结束标签其弹出,currentParent属性将会更新

逻辑图示如下:

代码如下:

// 标签名 a-aaa
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
// 命名空间标签 aa:aa-xxx
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
// 开始标签-捕获标签名 <div
const startTagOpen = new RegExp(`^<${qnameCapture}`);
// 结束标签-匹配标签结尾的 </div>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
// 匹配属性
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// 匹配标签结束的 > <br/>
const startTagClose = /^\s*(\/?)>/;
// 匹配 {{ }} 表达式
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;

function parseHTML(html) {
    const ELEMENT_TYPE = 1
    const TEXT_TYPE = 3
    const stack = []
    // 指向栈中最后一个元素
    let currentParent
    // 根节点
    let root
    function createASTElement(tag,attrs) {
        return {
            tag,
            type:ELEMENT_TYPE,
            attrs,
            children:[],
            parent:null
        }
    }
    function start(tagName,attrs) {
        let node = createASTElement(tagName,attrs)
        if(!root) root = node
        stack.push(node)
        if(currentParent) {
            node.parent = currentParent
            currentParent.children.push(node)
        }
        currentParent = node
    }
    function chars(text) {
        text = text.replace(/\s/g,"")
        text && currentParent.children.push({
            type: TEXT_TYPE,
            text
        })
    }
    function end(tagName) {
        stack.pop()
        currentParent = stack[stack.length - 1]
    }
    function advice(n) {
        html = html.substring(n);
    }
    function parseStarTag() {
        let start = html.match(startTagOpen)
        if(start) {
            let match = {
                tagName: start[1],
                attrs:[]
            }
            advice(start[0].length)
            // 解析属性
            let attr,end;
            while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
                advice(attr[0].length);
                match.attrs.push({name:attr[1],value:attr[3] || attr[4] || attr[5]})
            }
            if(end) advice(1)
            return match
        }
        return false
    }
    while(html) {
        // textEnd = 0说明是标签的开始或者结束位置,大于0说明是文本的结束位置
        let textEnd = html.indexOf('<')
        if(textEnd == 0) {
            let startTagMatch = parseStarTag()
            if(startTagMatch) {
                start(startTagMatch.tagName,startTagMatch.attrs)
                continue
            }
            let endTagMatch = html.match(endTag)
            if(endTagMatch) {
                end(endTagMatch[1])
                advice(endTagMatch[0].length)
                continue
            }
        }
        if(textEnd > 0) {
            let text = html.substring(0,textEnd)
            if(text) {
                chars(text)
                advice(text.length)
            }
        }
    }
    console.log(root);
}
export function compileToFunction(template) {
    // 1. 将template转化为ast语法树
    let ast = parseHTML(template);
}

 

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值