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);
相关推荐
小白学习日记40 分钟前
【复习】HTML常用标签<table>
前端·html
程序员大金43 分钟前
基于SpringBoot+Vue+MySQL的装修公司管理系统
vue.js·spring boot·mysql
丁总学Java1 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele1 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀2 小时前
CSS——属性值计算
前端·css
睡觉然后上课2 小时前
c基础面试题
c语言·开发语言·c++·面试
xgq2 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试
用户3157476081352 小时前
前端之路-了解原型和原型链
前端
永远不打烊2 小时前
librtmp 原生API做直播推流
前端
北极小狐2 小时前
浏览器事件处理机制:从硬件中断到事件驱动
前端