vue如何解析template模板

浅聊一下

在vue中,我们在template模板中写代码,vue将template模板中的代码解析成抽象语法树,再通过一系列操作变成DOM挂载在页面上,那么vue是如何识别template模板并且将其转为虚拟DOM的呢?

compile

在vue中,compile 是指将模板编译为渲染函数的过程。我们来看看在这个过程中,到底发生了什么?

js 复制代码
let template = `
    <div id="#app">
        <div @click="()=>console.log('xx')" :id="name">{{name}}</div>
        <h1 :name="title">玩转Vue3</h1>
        <p>编译原理</p>
    </div>
`

这是我们的一个模板,我们要将标签、属性、内容逐一分解出来,聪明的掘友可能已经想到了,这里使用正则来完成...

js 复制代码
function tokenizer(input) {
    let tokens = []
    let type = '' // 标签 属性 ....
    let val = ''
    // 逐一字符分词 
    for (let i = 0; i < input.length; i++) {
        let ch = input[i] // 每个字符 
        if (ch === '<') {
            push()
            if (input[i + 1] === '/') {
                type = 'tagend'
            } else {
                type= 'tagstart'
            }
        }
        if (ch === '>') {
            if (input[i-1] == '=') {
                // 箭头函数
            } else{
                push()
                type="text"
                continue
            }
        } else if (/[\s]/.test(ch)) {
            push() 
            type='props' 
            continue // div 拿完了 不需要再加ch 
        }
        val += ch 
    }
    console.log(tokens);
    return tokens;
    function push() {
        if (val) {
            // <div 
            if (type === 'tagstart') val = val.slice(1) // <div  div
            if (type === 'tagend') val = val.slice(2) // </div  div 
            tokens.push({
                type,
                val
            })
            val = ''
        }
    }
}
  1. 首先,定义了一个名为 tokenizer 的函数,该函数接收一个输入字符串 input,并返回一个 tokens 数组。

  2. 函数内部定义了 tokens 数组、type 和 val 两个变量,用于存储词法分析的结果和当前正在处理的标签类型及值。

  3. 使用 for 循环逐个遍历输入字符串中的字符。

  4. 在循环内部,根据当前字符的不同情况进行处理:

    • 如果当前字符是 "<",则调用 push 函数,并根据下一个字符是否为 "/" 来判断是标签的开始还是结束。
    • 如果当前字符是 ">",则调用 push 函数,并根据前一个字符是否是 "=" 来判断是箭头函数还是普通文本,然后继续处理下一个字符。
    • 如果当前字符是空白字符(包括空格、制表符等),则调用 push 函数,并将当前类型设置为属性,然后继续处理下一个字符。
    • 否则,将当前字符加入到 val 变量中。
  5. 在 push 函数中,将当前处理的值和类型推入 tokens 数组中,并根据类型对值进行修剪处理,然后重置 val 变量。

  6. 最后,输出 tokens 数组并返回。

来看看token数组

完美地将每一块内容都精准地分开,接下来的操作就是将这个token数组转为一个抽象语法树了,其实我在之前就已经讲过类似的内容了面试官:来道送分题 - 掘金 (juejin.cn),有点像将列表转为树...但是这里要复杂一点

js 复制代码
function parse(template) {
    // 分词
    const tokens = tokenizer(template);
    // console.log(tokens);
    let cur = 0
    let ast = {
        type: 'root',
        props: [],
        children: []
    }
    while(cur < tokens.length) {
        ast.children.push(walk())
    }
    return ast
    function walk() {
        let token = tokens[cur]
        if (token.type == 'tagstart') {
            let node = {
                type: 'element',
                tag: token.val,
                props: [],
                children: []
            }
            token = tokens[++cur]
            while (token.type !== 'tagend') {
                if (token.type == 'props') {
                    node.props.push(walk())
                } else {
                    node.children.push(walk())
                }
                token = tokens[cur]
            }
            cur++
            return node
        }

        if (token.type === 'tagend') {
            cur++
        } 
        if (token.type === 'text') {
            cur++ 
            return token
        }
        if (token.type === 'props') {
            cur++
            const [key,val] = token.val.replace('=', '~').split('~')
            return {
                key,
                val
            }
        }
    }
}
  1. 定义了一个名为 parse 的函数,该函数接收一个模板字符串作为参数,然后调用 tokenizer 函数对模板进行分词,得到 tokens 数组。

  2. 初始化 cur 变量为 0,表示当前处理的 token 索引,同时初始化一个空的 ast 对象作为最终的抽象语法树,其中包含一个根节点,具有类型、属性和子节点等字段。

  3. 使用 while 循环遍历 tokens 数组,并在循环内部调用 walk 函数来构建 AST 的子节点,将子节点添加到根节点的 children 数组中。

  4. walk 函数用于递归地处理每个 token,并根据不同类型的 token 构建对应的 AST 节点:

    • 如果 token 类型为 'tagstart',表示一个标签的开始,创建一个 element 类型的节点,包含标签名、属性和子节点等信息,然后递归处理其子节点。
    • 如果 token 类型为 'tagend',表示一个标签的结束,直接跳过。
    • 如果 token 类型为 'text',表示文本节点,直接返回该文本节点。
    • 如果 token 类型为 'props',表示属性节点,解析属性键值对并返回。
  5. 最后,返回构建好的抽象语法树 ast。

来看看输出

结尾

到这里我们已经完成从template模板到抽象语法树的过程了,接下来将该来到diff算法环节...

相关推荐
可乐鸡翅yeah_27 分钟前
业务中 M3U8 水印相关坑,硬水印和动态水印区别
前端·网络·数据库·ffmpeg·m3u8在线
lerhxx37 分钟前
AI 应用如何高效优雅地恢复中断?—— "连接解耦 + 状态持久化"
前端·javascript
计算机魔术师40 分钟前
METR 演示 AI 智能体如何篡改 Inspect 评估记录以掩盖不当行为
前端
前端snow1 小时前
ai agent --- 异步处理之 Rabbit MQ
前端
念何架构之路1 小时前
zap扩展生态与总结
java·前端·数据库
独孤九剑打醒他2 小时前
【原创开源】【概念设计】源 - 栅 - 漏 - 栅 - 源 横向双栅 MOS,低压交流多值逻辑芯片探索
前端·其他·架构·开源·硬件工程
mantou1322 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
默_笙2 小时前
🍕 一个主编、三个工种、两本手册:搭一支 AI 调研队
前端·javascript
纸片人2 小时前
Blender 建模 + Three.js 展示:和 AI 一起做一个光储充超充站数字孪生大屏
前端
智能直播2 小时前
网络RTMP拉流不卡顿、声音不同步?一文讲透缓冲区、时间戳与MEDAI V2实战调优
前端