• vue的模板编译原理


    vue的模板编译原理

    vue提供了模板语法,允许我们声明式地描述状态和DOM之间的绑定关系,比如<p>{{name}}<p>

    模板编译指的是模板编译成虚拟DOM的过程,vue会将模板编译成虚拟DOM渲染函数。

    模板 -> 模板编译 -> 渲染函数

    模板编译的主要目的是生成渲染函数,渲染函数的作用是每次执行时,会根据最新状态生成新的vnode

    将模板编译成渲染函数

    在这里插入图片描述
    解析器:将模板解析为AST(Abstract Syntax Tree 抽象语法树)
    优化器:遍历AST标记静态节点,因为静态节点不可变,不需要为打上标签的静态节点创建新的虚拟节点,直接克隆已有的虚拟节点。
    代码生成器:使用AST生成渲染函数。将AST转换成渲染函数中的内容,也就是代码字符串。将代码字符串放入函数(渲染函数)中,导出被外界使用。

    案例

    在这里插入图片描述

    1.模板确认

    假设如下代码,有eltemplaterender$mount

    //复杂案例
    let vue = new Vue({
        el: '#app',
        data() {
            return {
                a: 1,
                b: [1]
            }
        },
        render(h) {
            return h('div', { id: 'hhh' }, 'hello')
        },
        template: `<div id='hhh' style="aa:1;bb:2"><a>{{xxx}}{{ccc}}</a></div>`
    }).$mount('#app')
    
    console.log(vue)
    
    //脚手架创建的案例
    let vue = new Vue({
      render: h => h(App)
    }).$mount('#app')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    1. 渲染到哪个根节点上:判断有无el属性,有的话直接获取el根节点,没有的话调用$mount时去获取根节点
    2. 渲染哪个模板到根节点上去:是否调用render 函数传入了模板 render: h => h(App) -> <App></App>
    • 有render:这时候优先执行render函数,render优先级 > template
    • 无render:
      • 有template:template解析成render函数的所需的格式,并使用调用render函数渲染
      • 无template:el根节点的outerHTML解析成render函数的所需的格式,并使用调用render函数渲染

    3.渲染的方式:无论什么情况,最后都统一是要使用render函数渲染

    2.解析器-将模板解析成AST

    解析器-将模板解析成AST

    <div>
      <p>{{name}}</p>
    </div>
    
    • 1
    • 2
    • 3

    将上述模板解析成AST后,AST抽象语法树就是使用JS中的对象来描述一个节点,一个对象表示一个节点。

    {
      tag: "div"
      type: 1, //节点类型
      staticRoot: false,
      static: false,
      plain: true,
      parent: undefined, //存放父节点
      attrsList: [],
      attrsMap: {},
      children: [ //存放孩子节点
          {
          tag: "p"
          type: 1,
          staticRoot: false,
          static: false,
          plain: true,
          parent: {tag: "div", ...},
          attrsList: [],
          attrsMap: {},
          children: [{
              type: 2,
              text: "{{name}}",
              static: false,
              expression: "_s(name)"
          }]
        }
      ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    解析器的工作原理

    解析器内部分也分几个子解析器,如HTML解析器、文本解析器等。

    HTML解析器的作用是解析HTML,在解析HTML的过程中不断触发各种钩子函数,

    • 开始标签的钩子函数中可以构建元素类型的节点
    • 文本钩子函数中可以构建文本类型的节点
    • 注释钩子函数中可以构建注释类型的节点
    • 结束标签钩子函数

    文本解析器是对HTML解析出来的文本进行二次加工,比如插值语法{{}}

    如何确定DOM之间的层级关系?使用栈
    在触发开始标签的钩子函数时,如果当前标签不是自闭合标签,就pushstack
    在触发结束标签的钩子函数时,就从栈中pop出战

    HTML解析器的原理时一小段一小段地截取模板字符串,每截取一小段字符串,就会根据截取出来的字符串类型触发不同的钩子函数,直到模板字符串截空停止。
    这样HTML解析器运行完毕后,我们就可以得到一个完整的带DOM层级关系的AST。

    3.优化器-标记AST中的静态节点

    标记静态子树的好处

    • 每次重新渲染时,不需要为静态子树创建新虚拟子树,克隆已存在的静态子树
    • 在虚拟DOM中打补丁(patching)的过程可以跳过 ,静态子树是不可变的

    优化器的内部实现主要分两步用递归的方式将所有节点添加 static 属性,true表示是静态的,false表示不是静态的。

    • 在AST中找出所有静态节点并打上标记
      静态节点:DOM不会发生变化的节点
      通过递归的方式从上向下标记静态节点,如果一个节点被标记为静态节点,但它的子节点却被标记为动态节点,就说明该节点不是静态节点,可以将它改为动态节点。静态节点的特征是它的子节点也必须是静态的。

    静态根节点也是静态节点

    • 在AST中找出所有静态根节点并打上标记
      静态根节点:子节点全是静态节点的节点
      使用递归从上向下寻找,在寻找的过程中遇见的第一个静态节点就为静态根节点,同时不继续往下找。

    如果一个静态根节点的子节点只有一个文本节点或没有子节点,那么不会标记成静态根节点,即使他们是,因为优化成本大于收益

    怎么判断是否静态节点?
    在将模板字符串解析成AST的时候,会根据不同的文本类型设置一个 type

    type说明是否时静态节点
    1元素节点进行一些排除
    2带遍历的动态文本节点不是
    3不带遍历的纯文本节点

    4.代码生成器-将AST转化成渲染函数中的代码字符串

    代码生成器的作用:将AST转化成渲染函数中的代码字符串

    <div>
      <p>{{name}}</p>
    </div>
    //生成的render渲染函数
    {
      render: `with(this){return _c('div',[_c('p',[_v(_s(name))])])}`
    }
    //格式化后
    with(this){
      return _c(
        'div',
        [
          _c(
            'p',
            [
              _v(_s(name))
            ]
          )
        ]
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    生成代码字符串是一个递归的过程,从顶向下依次处理每一个AST节点。
    节点有三种类型,分别对应三种不同的创建方法与别名。

    类型创建方法别名
    元素节点createElement_c
    文本节点createTextVNode_v
    注释节点createEmptyVNode_e

    渲染函数可以生成VNode的原因:渲染函数其实是执行了createElement,而createElement可以创建VNode。

    代码字符串的拼接过程
    递归AST来生成字符串,最先生成根节点,然后在子节点字符串生成后,将其拼接在根节点的参数中,子节点的子节点拼接在子节点的参数中,一层层拼接。

  • 相关阅读:
    C#/.NET/.NET Core优秀项目和框架2023年12月简报
    Typescript面向对象(接口、类、多态、重写、抽象类、访问修饰符)
    Qt5 QML TreeView currentIndex当前选中项的一些问题
    神经网络试题答案,神经网络考试例题
    183. 从不订购的客户—not in()、左连接
    【实践篇】Redis最强Java客户端Redisson
    十沣科技船舶运动仿真 助力我国船舶研发设计提效降本
    Delta3D(9)教程:添加消息发送和可激活体
    Qt应用开发(基础篇)——树结构视图 QTreeView
    Qt入门教程:单选按钮(QRadioButton)
  • 原文地址:https://blog.csdn.net/qq_41370833/article/details/125467581