沐游虞笔记
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 课程导读-必看
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速diff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
    • 静态提升
    • 预字符串化
    • 缓存内联事件处理函数
    • block tree
    • 补丁标记
  • 组件name作用
  • Vue项目性能优化
  • 路由传参方式
  • vue3笔面试题汇总
luzhichang
2024-09-27
目录

模板编译提升

# 模板编译提升

面试题:说一下 Vue3 在进行模板编译时做了哪些优化?

  1. 静态提升
  2. 预字符串化
  3. 缓存事件处理函数
  4. Block Tree
  5. PatchFlag

# 静态提升

静态提升 Static Hoisting,在模板编译阶段识别并提升不变的静态节点到渲染函数外部,从而减少每次渲染时的计算量。被提升的节点无需重复创建。

哪些节点会被提升

  1. 元素节点
  2. 没有绑定动态内容的节点

一个提升的示例

<template>
  <div>
    <p>这是一个静态的段落。</p>
    <p>{{ dynamicMessage }}</p>
  </div>
</template>

在 Vue2 时期不管是静态节点还是动态节点,都会编译为 创建虚拟节点函数 的调用。

with(this) {
  return createElement('div', [
    createElement('p', [createTextVNode("这是一个静态的段落。")]),
    createElement('p', [createTextVNode(toString(dynamicMessage))])
  ])
}

Vue3 中,编译器会对静态内容的编译结果进行提升:

const _hoisted_1 = /*#__PURE__*/createStaticVNode("<p>这是一个静态的段落。</p>", 1);

export function render(_ctx, _cache) {
  return (openBlock(), createElementBlock("div", null, [
    _hoisted_1,
    createElementVNode("p", null, toDisplayString(_ctx.dynamicMessage), 1 /* TEXT */)
  ]))
}

除了静态节点,静态属性也是能够提升的,例如:

<template>
  <button class="btn btn-primary">{{ buttonText }}</button>
</template>

在这个模板中,虽然 button 是一个动态节点,但是属性是固定的,因此这里也有优化的空间:

// 静态属性提升
const _hoisted_1 = { class: "btn btn-primary" };

export function render(_ctx, _cache) {
  return (openBlock(), createElementBlock("button", _hoisted_1, toDisplayString(_ctx.buttonText), 1 /* TEXT */))
}

# 预字符串化

当编译器遇到大量连续的静态内容时,会直接将其编译为一个普通的字符串节点。例如:

<template>
  <div class="menu-bar-container">
    <div class="logo">
      <h1>logo</h1>
    </div>
    <ul class="nav">
      <li><a href="">menu</a></li>
      <li><a href="">menu</a></li>
      <li><a href="">menu</a></li>
      <li><a href="">menu</a></li>
      <li><a href="">menu</a></li>
    </ul>
    <div class="user">
      <span>{{ user.name }}</span>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
const user = ref({
  name: "zhangsan",
});
</script>

编译结果中和静态提升相关的部分:

const _hoisted_1 = { class: "menu-bar-container" }
const _hoisted_2 = /*#__PURE__*/_createStaticVNode("<div class=\"logo\"><h1>logo</h1></div><ul class=\"nav\"><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li></ul>", 2)
const _hoisted_4 = { class: "user" }

其中的 _hoisted_2 就是将连续的静态节点编译为了字符串。

思考🤔:这样有什么好处呢?

答案:当大量的连续的静态节点被编译为字符串节点后,整体的虚拟 DOM 节点数量就少了,自然而然 diff 的速度就更快了。

Vue2:

vue2

Vue3:

vue3

第二个好处就是在 SSR 的时候,无需重复计算和转换,减少了服务器端的计算量和处理时间。

思考🤔:大量连续的静态内容时,会启用预字符串化处理,大量连续的边界在哪里?

答案:在 Vue3 编译器内部有一个阀值,目前是 10 个节点左右会启动预字符串化。

<template>
  <div class="menu-bar-container">
    <div class="logo">
      <h1>logo</h1>
      <h1>logo</h1>
      <h1>logo</h1>
      <h1>logo</h1>
      <h1>logo</h1>
      <h1>logo</h1>
      <h1>logo</h1>
      <h1>logo</h1>
      <h1>logo</h1>
    </div>
    <div class="user">
      <span>{{ user.name }}</span>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
const user = ref({
  name: "zhangsan",
});
</script>

# 缓存内联事件处理函数

模板在进行编译的时候,会针对内联的事件处理函数做缓存。例如:

<button @click="count++">plus</button>

在 Vue2 中,每次渲染都会为这个内联事件创建一个新的函数,这会产生不必要的内存开销和性能损耗。

render(ctx){
  return createVNode("button", {
    // 每次渲染的时候,都会创建一个新的函数
    onClick: function($event){
      ctx.count++;
    }
  })
}

在 Vue3 中,为了优化这种情况,编译器会自动为内联事件处理函数生成缓存代码。

render(ctx, _cache){
  return createVNode("button", {
    // 如果缓存里面有,直接从缓存里面取
    // 如果缓存里面没有,创建一个新的事件处理函数,然后将其放入到缓存里面
    onClick: cache[0] || (cache[0] = ($event) => (ctx.count++))
  })
}

思考🤔:为什么仅针对内联事件处理函数进行缓存?

答案:非内联事件处理函数不需要缓存,因为非内联事件处理函数在组件实例化的时候就存在了,不会在每次渲染时重新创建。缓存机制主要是为了解决内联事件处理函数在每次渲染的时候重复创建的问题。

# block tree

Vue2 在对比新旧树的时候,并不知道哪些节点是静态的,哪些是动态的,因此只能一层一层比较,这就浪费了大部分时间在比对静态节点上,例如下面的代码:

<form>
  <div>
    <label>账号:</label>
    <input v-model="user.loginId" />
  </div>
  <div>
    <label>密码:</label>
    <input v-model="user.loginPwd" />
  </div>
</form>

20200929172002

每次状态更新时,Vue2 需要遍历整个虚拟 DOM 树来寻找差异。这种方法虽然通用,但在大型组件或复杂页面中,性能损耗会比较明显,因为它浪费了大量时间在静态节点的比较上。

思考🤔:前面不是说静态节点会提升么?

答案:静态提升解决的是不再重复生成静态节点所对应的虚拟DOM节点。现在要解决的问题是虚拟DOM树中静态节点比较能否跳过的问题。

什么是Block

一个 Block 本质上也是一个虚拟 DOM 节点,不过该虚拟 DOM 节点上面会多出来一个 dynamicChildren 属性,该属性对应的值为数组,数组里面存储的是动态子节点。以上面的代码为例,form 对应的虚拟 DOM 节点就会存在 dynamicChildren 属性:

20200929172555

有了 block 之后,就不需要再像 Vue2 那样一层一层,每个节点进行对比了,对比的粒度变成了直接找 dynamicChildren 数组,然后对比该数组里面的动态节点,这样就很好的实现了跳过静态节点比较。

哪些节点会成为 block 节点?

  1. 模板中的根节点都会是一个 block 节点。

    <template>
    	<!-- 这是一个block节点 -->
    	<div>
        <p>{{ bar }}</p>
      </div>
    	<!-- 这是一个block节点 -->
    	<h1>
        <span :id="test"></span>
      </h1>
    </template>
    
  2. 任何带有 v-if、v-else-if、v-else、v-for 指令的节点,也需要作为 block 节点。

    答案:因为这些指令会让虚拟DOM树的结构不稳定。

    <div>
      <section v-if="foo">
      	<p>{{ a }}</p>
      </section>
      <div v-else>
        <p>{{ a }}</p>
      </div>
    </div>
    

    按照之前的设计,div是一个 block 节点,收集到的动态节点只有 p. 这意味着无论 foo 是 true 还是 false,最终更新只会去看 p 是否发生变化,从而产生 bug.

    解决方案也很简单,让带有这些指令的节点成为一个 block 节点即可

    block(div)
    	- block(section)
    	- block(div)
    

    此时这种设计,父级block除了收集动态子节点以外,还会收集子block节点。

    多个 block 节点自然就形成了树的结构,这就是 block tree.

# 补丁标记

补丁标记 PatchFlags,这是 Vue 在做节点对比时的近一步优化。

即便是动态的节点,一般也不会是节点所有信息(类型、属性、文本内容)都发生了更改,而仅仅只是一部分信息发生更改。

之前在 Vue2 时期对比每一个节点时,并不知道这个节点哪些相关信息会发生变化,因此只能将所有信息依次比对,例如:

<div :class="user" data-id="1" title="user name">
  {{user.name}}
</div>

在 Vue2 中:

  • 全面对比:会逐个去检查节点的每个属性(class、data-id、title)以及子节点的内容
  • 性能瓶颈:这种方式自然就存在一定的性能优化空间
20200929172805

在 Vue3 中,PatchFlag 通过为每个节点生成标记,显著优化了对比过程。编译器在编译模板时,能够识别哪些属性或内容是动态的,并为这些动态部分生成特定的标记。

Vue3 的 PatchFlag 包括多种类型,每种类型标记不同的更新需求:

  • TEXT:表示节点的文本内容可能会发生变化。
  • CLASS:表示节点的 class 属性是动态的,可能会发生变化。
  • STYLE:表示节点的 style 属性是动态的,可能会发生变化。
  • PROPS:表示节点的一个或多个属性是动态的,可能会发生变化。
  • FULL_PROPS:表示节点有多个动态属性,且这些属性不是简单的静态值。
  • HYDRATE_EVENTS:表示节点的事件监听器是动态的,需要在客户端进行水合处理。
  • STABLE_FRAGMENT:表示节点的子节点顺序稳定,允许按顺序进行更新。
  • KEYED_FRAGMENT:表示节点的子节点带有 key,可以通过 key 进行高效的更新。
  • UNKEYED_FRAGMENT:表示节点的子节点无 key,但可以通过简单的比较进行更新。

例如上面的代码,编译出来的函数:

function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", {
    class: _normalizeClass($setup.user),
    "data-id": "1",
    title: "user name"
  }, _toDisplayString($setup.user.name), 3 /* TEXT, CLASS */))
}

通过这些标记,Vue3 在更新时不再需要对每个属性都进行全面的对比,而是只检查和更新那些被标记为动态的部分,从而显著减少了不必要的计算开销。

面试题:说一下 Vue3 在进行模板编译时做了哪些优化?

参考答案:

Vue3 的编译器在进行模板编译的时候,主要做了这么一些优化:

  1. 静态提升:解决的是静态内容不要重复生成新的虚拟 DOM 节点的问题
  2. 预字符串化:解决的是大量的静态内容,干脆虚拟 DOM 节点都不要了,直接生成字符串,虚拟 DOM 节点少了,diff 的时间花费也就更少。
  3. 缓存内联事件处理函数:每次运行渲染函数时,内联的事件处理函数没有必要重新生成,这样会产生不必要的内存开销和性能损耗。所以可以将内联事件处理函数缓存起来,在下一次执行渲染函数的时候,直接从缓存中获取。
  4. Block Tree:解决的是跳过静态节点比较的问题。
  5. 补丁标记:能够做到即便动态节点进行比较,也只比较有变化的部分的效果。

-EOF-

模板编译器
组件name作用

← 模板编译器 组件name作用→

Theme by Vdoing | Copyright © 2021-2024 蜀ICP备2024068710号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式