Vue.js技术内幕 12-14 编译和优化
编译和优化
编译
- Vue3编译场景分为Web编译和SSR编译。
- Web编译
- 编译目标:将.vue文件中的
<template>或模板字符串,转换为一个高性能的渲染函数render() - 编译流程
- 1.解析template生成AST
- 2.AST转换
- 3.生成代码
- 编译目标:将.vue文件中的
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的根节点。
-
- 1.词法分析:将模板字符串拆解成一个个最小的、有意义的单元(令牌,tokens)。如
2. 转换Transform,优化与加工AST
- 目标:对原始AST进行深度遍历与修改,通过语法分析,创建语义和信息更加丰富的代码生成节点,为最终生成代码做准备。
- 输入:原始AST
- 输出:优化后的AST
- AST对象的转换过程
- 首先,执行getBaseTransformPreset函数获取节点和指令转换的函数。
- 然后,调用transform函数做AST转换,并把这些节点和指令的转换函数作为配置的属性参数传入。
- transform核心流程四步:
-
创建transform上下文(transform过程的一些配置如节点和指令的转换函数;transform过程的一些状态数据如当前处理的AST节点,索引,父节点;辅助函数;修改context对象的函数;)
-
遍历AST节点,执行对应转换函数如指令、表达式、元素节点transformElement、文本节点
- 元素节点转换函数transformElement:
- 判断节点是不是Block节点,区分动静节点,提升diff效率
- 处理节点的props,指令、动态属性、更新标识patchFlag
- 处理节点的children
- 根据更新标识从动态属性中获取flag对应的名字,从而生成注释。将动态属性数组转化为动态节点字符串,便于对后续对节点生成代码逻辑的处理。
- 通过createVNodeCall创建了实现VNodeCall接口的代码生成节点。
- 表达式节点转换函数transformExpression:转换插值和元素指令中的动态表达式,把简单的表达式对象转换成复合表达式对象
- Text节点转换函数transformText:合并一些相邻的文本节点,然后为内部每一个文本节点创建一个代码生成节点。
- 条件节点转换函数transformIf:处理v-if节点以及v-if相邻节点,如v-else-if、v-else,并且会走不同的处理逻辑
- 元素节点转换函数transformElement:
-
静态提升
- 将模板中的静态节点(Static Node)或静态属性(Static Props)提取到组件渲染函数之外,只在应用启动时创建一次,然后在每次渲染时重复使用,而不是重新创建。
- 节点转换完成后,会判断编译配置中是否配置了hoistStatic,如果是就会执行hoistStatic做静态提升。
-
创建根代码生成节点
- 目的:为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技术内幕-黄轶]
宝剑锋从磨砺出,梅花香自苦寒来。

浙公网安备 33010602011771号