Vue.js技术内幕 12-14 编译和优化

编译和优化

编译

  • Vue3编译场景分为Web编译和SSR编译。
  • Web编译
    • 编译目标:将.vue文件中的<template>或模板字符串,转换为一个高性能的渲染函数render()
    • 编译流程
      • 1.解析template生成AST
      • 2.AST转换
      • 3.生成代码

1. 解析Parse,从字符串到AST

  • 目标:将原始的模板字符串解析成一个抽象语法树AST。
    • AST,用JS对象结构描述模板语法结构的树,每个节点代表模板中的一个元素、插值表达式、指令等,会有type字段描述节点类型,tag字段描述节点标签,props字段描述节点属性,loc字段描述节点代码位置相关信息,children字段指向它的子节点对象数组。
    • AST是一个虚拟节点。Vue3支持多个根节点,但树必须有一个根节点,所以需要虚拟节点作为AST的根节点。
  • 输入:模板字符串
  • 输出:原始AST

  • 工作流程:
    • 1.词法分析:将模板字符串拆解成一个个最小的、有意义的单元(令牌,tokens)。如<, div, @click, {, {, msg, }, }等。
    • 2.语法分析:根据Vue等模板语法规则,将tokens组合成一个树形结构,会标识出标签、属性、指令、插值表达式、文本内容等。
    {
      type: 1, // 节点类型: 1-元素, 2-表达式, 3-文本
      tag: 'div',
      props: [
        {
          type: 6,
          name: 'id',
          value: {
            type: 7,
            content: 'dynamicId'
          }
        }
      ],
      children: [
        {
          type: 5, // 表达式节点
          content: {
            type: 4,
            content: 'message'
          }
        }
      ]
    }
    
    • 根据模板字符串构建AST对象,是通过执行baseParse函数完成的
      • 1.创建解析上下文createParseContext

        • 解析上下文,是一个JS对象,它维护着解析过程中的上下文。如options-解析相关配置,column-当前代码的列号,source-当前代码。
      • 2.解析子节点parseChildren

        • 目的:解析模板并创建AST节点数组。
        • 流程:它有两个主要流程,第一个是自顶向下分析代码,生成AST节点数组nodes;第二个是空白字符处理,用于提高编译的效率。
          • 注释节点的解析,parseComment,注释结束符、注释内容、嵌套注释
          • 插值的解析,parseInterpolation,parseTextData,插值结束分隔符、插值内容、
          • 普通文本的解析,parseText,parseTextData,文本结束的位置、文本内容
          • 元素节点的解析,parseElement,解析开始标签parseTag,解析子节点parseChildren,解析闭合标签
          • 空白字符的处理,移除、压缩
      • 3.创建AST根节点createRoot,返回一个JS对象作为AST的根节点。


2. 转换Transform,优化与加工AST

  • 目标:对原始AST进行深度遍历与修改,通过语法分析,创建语义和信息更加丰富的代码生成节点,为最终生成代码做准备。
  • 输入:原始AST
  • 输出:优化后的AST

  • AST对象的转换过程
    • 首先,执行getBaseTransformPreset函数获取节点和指令转换的函数。
    • 然后,调用transform函数做AST转换,并把这些节点和指令的转换函数作为配置的属性参数传入。

  • transform核心流程四步:
    1. 创建transform上下文(transform过程的一些配置如节点和指令的转换函数;transform过程的一些状态数据如当前处理的AST节点,索引,父节点;辅助函数;修改context对象的函数;)

    2. 遍历AST节点,执行对应转换函数如指令、表达式、元素节点transformElement、文本节点

      • 元素节点转换函数transformElement:
        1. 判断节点是不是Block节点,区分动静节点,提升diff效率
        2. 处理节点的props,指令、动态属性、更新标识patchFlag
        3. 处理节点的children
        4. 根据更新标识从动态属性中获取flag对应的名字,从而生成注释。将动态属性数组转化为动态节点字符串,便于对后续对节点生成代码逻辑的处理。
        5. 通过createVNodeCall创建了实现VNodeCall接口的代码生成节点。
      • 表达式节点转换函数transformExpression:转换插值和元素指令中的动态表达式,把简单的表达式对象转换成复合表达式对象
      • Text节点转换函数transformText:合并一些相邻的文本节点,然后为内部每一个文本节点创建一个代码生成节点。
      • 条件节点转换函数transformIf:处理v-if节点以及v-if相邻节点,如v-else-if、v-else,并且会走不同的处理逻辑
    3. 静态提升

      • 将模板中的静态节点(Static Node)或静态属性(Static Props)提取到组件渲染函数之外,只在应用启动时创建一次,然后在每次渲染时重复使用,而不是重新创建。
      • 节点转换完成后,会判断编译配置中是否配置了hoistStatic,如果是就会执行hoistStatic做静态提升。
    4. 创建根代码生成节点

      • 目的:为root这个虚拟的AST根节点创建一个代码生成节点,若root的子节点children是单个元素节点,将其转换成一个Block,把child的codegenNode赋值给root的codegenNode,若root的子节点children是多个节点,那么返回一个fragement的代码生成节点,并赋值给root的codegenNode。

  • Vue3编译优化点
    • patchFlag,补丁标志,标记出哪些属性是动态的,渲染时可以根据patchFlag知道更新节点的哪些部分,减少虚拟DOM Diff时的比较开销。
    • Hoist Static,静态提升,编译器会识别出纯静态的节点及其子树,即不包含任何动态绑定(如指令、插值、动态属性)的节点,将镜静态节点提升到渲染函数之后,避免每次重新渲染时重复创建相同的静态节点,降低内存占用,同时可跳过静态节点,减少Diff比较大节点数量。
    • 事件处理器缓存,缓存内联事件处理器,避免每次渲染都创建新的函数。
    • Block Tree 块树,Tree Flattening 树结构打平
      • Block,Vue3将模板中所有带有动态结构指令(如v-if,v-for)的节点标记为一个Block块,一个模板可以有多块。
      • 动态子节点数组,每个Block会收集其所有带有patchFlag的动态子节点,形成一个扁平的数组。
      • 更新时,渲染器可以直接遍历这个扁平的动态子节点数组来更新动态节点,无需递归遍历整棵完整的树。

3. 生成代码,从AST到Render函数

  • 目标:将优化后的AST转换为可执行的JS代码字符串,即render函数。render函数的作用是在运行时根据当前组件的状态(响应式数据)创建对应的虚拟DOM(VNode)。
  • 输入:优化后的AST
  • 输出:渲染函数代码字符串
  • 工作流程:遍历AST,根据节点类型拼接生成不同的JS代码字符串。
    • 创建了codegen上下文,它负责维护整个代码生成中的一些状态数据,如当前代码和缩进,以及提供一些修改上下文数据的辅助函数。
    • 生成一些预设代码,比如引入辅助函数、生成静态提升相关代码等。
    • 生成与渲染函数相关的代码,比如生成渲染函数的名称和参数,生成资源声明的代码,生成创建vnode树的代码等。



参考&感谢各路大神

  • [Vue.js技术内幕-黄轶]
posted @ 2024-12-09 12:17  安静的嘶吼  阅读(12)  评论(0)    收藏  举报