vue中template到VDOM发生了什么

前言

小伙伴们在使用vue的时候,在模板template中写入一段html代码,vue将template中的代码解析并将其转化为虚拟DOM,这其中发生了什么呢?

compiler

首先来说说compiler,在 Vue 3 中,编译器(compiler)的主要作用是将模板(template)转换为渲染函数(render function),以及将模板中的指令、插值等转换为对应的代码,当我们执行渲染函数时,就会返回VDOM

这里我们输入一段这样的template

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

tokenizer

这是第一步,当我们输入一段模板template后,tokenizer函数将模板template进行分词,得到一个tokens数组,分词的规则是将标签、属性、内容一一分解出来,接下来我们来看一段代码:

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 

    }
    
    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() 函数将之前收集的词法单元推入 tokens 数组中,并根据下一个字符判断当前 < 是起始标签还是结束标签,分别设置 type'tagstart''tagend'
  5. 如果当前字符为 >,则也调用 push() 函数将之前收集的词法单元推入 tokens 数组中,并设置 type'text',表示文本节点。如果前一个字符是 =,则说明可能是箭头函数,这里我们没有写出
  6. 如果当前字符是空白符(空格、制表符、换行符等),则同样调用 push() 函数将之前收集的词法单元推入 tokens 数组中,并设置 type'props',表示属性。
  7. 如果以上条件都不满足,则将当前字符加入到 val 中,用于构建当前词法单元的值。
  8. 最后返回 tokens 数组作为输出。
  9. push() 函数中,如果 val 不为空,则根据当前词法单元的类型进行一些处理,如去除起始标签和结束标签的 <</,然后将该词法单元推入 tokens 数组中,并清空 val

分完词后,将会返回一个tokens数组,我们输出一下这个数组来看看结果:

parse

调用parse函数,是我们将要进行的第二个操作,将tokens转化为一个抽象语法树ast,我们来看看代码:

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. function parse(template) { ... }: 这是一个名为 parse 的函数,它接收一个模板字符串作为参数,然后调用 tokenizer 函数对模板字符串进行分词,并利用分词结果生成抽象语法树(AST)。

  2. const tokens = tokenizer(template);: 调用 tokenizer 函数将模板字符串转换成一个 tokens 数组,tokens 数组中包含了模板字符串中的各个词法单元。

  3. let cur = 0: cur 用于记录当前处理的 token 在 tokens 数组中的索引。

  4. let ast = { type: 'root', props: [], children: [] }: 创建一个名为 ast 的对象,表示整个模板的抽象语法树。ast 对象包含了 type(类型)、props(属性)和 children(子节点)三个字段,初始化为一个根节点。

  5. while(cur < tokens.length) { ... }: 使用 while 循环遍历 tokens 数组中的每个 token,并通过调用 walk 函数来递归地构建抽象语法树。

  6. function walk() { ... }: walk 函数用于递归地构建抽象语法树的节点。它根据当前处理的 token 类型进行不同的处理逻辑,并返回构建好的节点。

    • 当 token 类型为 tagstart 时,表示遇到了标签的开始,此时会创建一个元素节点,处理该标签的属性和子节点,并返回该节点。
    • 当 token 类型为 tagend 时,表示遇到了标签的结束,跳过处理。
    • 当 token 类型为 text 时,表示遇到了文本节点,直接返回该节点。
    • 当 token 类型为 props 时,表示遇到了属性节点,将属性键值对提取出来,并返回键值对对象。

使用parse会得到一个抽象语法树ast,接下来我们调用这些函数来看看输出结果:

js 复制代码
function compiler(template) {
    const ast = parse(template)
    console.log(ast);
}
const renderFunction = compiler(template);
相关推荐
糕冷小美n2 小时前
elementuivue2表格不覆盖整个表格添加固定属性
前端·javascript·elementui
小哥不太逍遥2 小时前
Technical Report 2024
java·服务器·前端
沐墨染3 小时前
黑词分析与可疑对话挖掘组件的设计与实现
前端·elementui·数据挖掘·数据分析·vue·visual studio code
anOnion3 小时前
构建无障碍组件之Disclosure Pattern
前端·html·交互设计
threerocks3 小时前
前端将死,Agent 永生
前端·人工智能·ai编程
问道飞鱼4 小时前
【前端知识】Vite用法从入门到实战
前端·vite·项目构建
爱上妖精的尾巴4 小时前
8-10 WPS JSA 正则表达式:贪婪匹配
服务器·前端·javascript·正则表达式·wps·jsa
常利兵4 小时前
吃透Java操作符高阶:位操作符+赋值操作符全解析(Java&C区别+实战技巧+面试考点)
java·c语言·面试
Zhencode4 小时前
Vue3响应式原理之ref篇
vue.js
小疙瘩5 小时前
element-ui 中 el-upload 多文件一次性上传的实现
javascript·vue.js·ui