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算法环节...

相关推荐
勿语&20 分钟前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
黄尚圈圈21 分钟前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水1 小时前
简洁之道 - React Hook Form
前端
正小安3 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光5 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   5 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   5 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web5 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常5 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式