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);
相关推荐
二哈喇子!1 小时前
Vue3生命周期
前端·javascript·vue.js
运维帮手大橙子5 小时前
完整的登陆学生管理系统(配置数据库)
java·前端·数据库·eclipse·intellij-idea
_Kayo_6 小时前
CSS BFC
前端·css
二哈喇子!7 小时前
Vue3 组合式API
前端·javascript·vue.js
二哈喇子!8 小时前
Vue 组件化开发
前端·javascript·vue.js
C4程序员9 小时前
北京JAVA基础面试30天打卡03
java·开发语言·面试
chxii9 小时前
2.9 插槽
前端·javascript·vue.js
姑苏洛言9 小时前
扫码点餐小程序产品需求分析与功能梳理
前端·javascript·后端
Freedom风间10 小时前
前端必学-完美组件封装原则
前端·javascript·设计模式
Java技术小馆10 小时前
PromptPilot打造高效AI提示词
java·后端·面试