首页IT科技vue3优化点(从面试题入手,畅谈 Vue 3 性能优化)

vue3优化点(从面试题入手,畅谈 Vue 3 性能优化)

时间2025-04-29 21:28:36分类IT科技浏览3414
导读:前言 今年又是一个非常寒冷的冬天,很多公司都开始人员精简。市场从来不缺前端,但对高级前端的需求还是特别强烈的。一些大厂的面试官为了区分候选人对前端领域能力的深度,经常会在面试过程中考察一些前端框架的源码性知识点。Vuejs 作为世界顶尖的框架之一,几乎在所有的面试场景中或多或少都会被提及。...

前言

今年又是一个非常寒冷的冬天          ,很多公司都开始人员精简          。市场从来不缺前端               ,但对高级前端的需求还是特别强烈的               。一些大厂的面试官为了区分候选人对前端领域能力的深度     ,经常会在面试过程中考察一些前端框架的源码性知识点     。Vuejs 作为世界顶尖的框架之一          ,几乎在所有的面试场景中或多或少都会被提及     。

笔者之前在蚂蚁集团就职               ,对于 Vue 3 的考点还是会经常问的               。接下来     ,我将根据多年的面试以及被面试经验     ,为小伙伴们梳理最近大厂爱问的 Vue 3 问题          。然后我们再根据问题举一反三               ,深入学习 Vue 3 源码知识!

场景一:Vue 3.x 相对于 Vue 2.x 做了那些额外的性能优化?

要理解 Vue 3 的性能优化的核心          ,就需要了解 Vuejs 的核心设计理念     。我们知道 Vuejs 官网上有一句话总结的特别到位:渐进式 JavaScript 框架     ,易学易用               ,性能出色          ,适用于场景丰富的 Web 框架               。 其实我们的答案就蕴藏在这句话里          。

首先,我们知道当我们浏览 Web 网页时               ,有两类场景会制约 Web 网页的性能

网络传输的瓶颈 CPU的瓶颈

所以要回答这个问题               ,就可以直接从这两方面入手。

网络传输的瓶颈优化

对于前端框架而言,制约网络传输的因素最大的就是代码体积          ,代码体积越大               ,传输效率越慢               。尤其对于 SPA 单页应用的 CSR(客户端渲染) 而言               。一个大体积的框架资源     ,就意味着用户需要等待白屏的时间越长。而 Vue 3 在减少源码体积方面做的最多的就是通过精细化的 Tree-Shacking 机制来构建 渐进式 代码          。

1. /*#__PURE__*/ 标记

我们知道 Tree-Shaking 可以删除一些 DC(dead code) 代码               。但是对于一些有副作用的函数代码          ,却是无法进行很好的识别和删除               ,举个例子:

foo() function foo(obj) { obj?.a }

上述代码中     ,foo 函数本身是没有任何意义的     ,仅仅是对对象 obj 进行了属性 a 的读取操作               ,但是 Tree-Shaking 是无法删除该函数的          ,因为上述的属性读取操作可能会产生副作用     ,因为 obj 可能是一个响应式对象               ,我们可能对 obj 定了一个 getter 在 getter 中触发了很多不可预期的操作     。

如果我们确认 foo 函数是一个不会有副作用的纯净的函数          ,那么这个时候 /*#__PURE__*/ 就派上用场了,其作用就是告诉打包器               ,对于foo函数的调用不会产生副作用               ,你可以放心地对其进行 Tree-Shaking          。

另外,值得一提的是          ,在 Vue 3 源码中               ,包含了大量的 /*#__PURE__*/ 标识符     ,可见 Vue 3 对源码体积的控制是多么的用心!

2. 特性开关

在 Vue 3 源码中的 rollup.config.mjs 中有这样一段代码:

{ __FEATURE_OPTIONS_API__: isBundlerESMBuild ? `__VUE_OPTIONS_API__` : true, }

其中__FEATURE_OPTIONS_API__是一个构建时的环境变量          ,我们知道 Vue 3 在某些 API 方面是兼容 Vue 3 写法的               ,比如 Options API               。但是如果我们在项目中仅仅使用 Compositon API 而不想使用 Options API 那么我们就可以在项目构建时关闭这个选项     ,从而减少代码体积     。我们看看这个变量在 Vue 3 源码中是如何使用的:

// 兼容 2.x 选项式 API if (__FEATURE_OPTIONS_API__) { currentInstance = instance pauseTracking() applyOptions(instance, Component) resetTracking() currentInstance = null }

用户可以通过设置__VUE_OPTIONS_API__预定义常量的值来控制是否要包含这段代码     。通常用户可以使用webpack.DefinePlugin插件来实现:

// webpack.DefinePlugin 插件配置 new webpack.DefinePlugin({ __VUE_OPTIONS_API__: JSON.stringify(true) // 开启特性 })

除此之外     ,类似的开发环境会通过 __DEV__ 来输出告警规则               ,而在生产环境剔除这些告警降低构建后的包体积都是类似的手段:

if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) }

CPU 瓶颈优化

当项目变得庞大          、组件数量繁多时          ,就容易遇到CPU的瓶颈               。主流浏览器刷新频率为60Hz     ,即每(1000ms / 60Hz)16.6ms浏览器刷新一次          。

我们知道               ,JS可以操作DOM          ,GUI渲染线程与JS线程是互斥的     。所以JS脚本执行浏览器布局               、绘制不能同时执行               。

在每16.6ms时间内,需要完成如下工作:

JS脚本执行 ----- 样式布局 ----- 样式绘制

当JS执行时间过长               ,超出了16.6ms               ,这次刷新就没有时间执行样式布局样式绘制了,也就出现了丢帧的情况          ,会发生卡顿          。

为了解决庞大元素组件渲染     、更新卡顿的问题               ,Vue 的策略是一方面采用了组件级的细粒度更新     ,控制更新的影响面:Vue 3 中          ,每个组件都会生成一个渲染函数               ,这些渲染函数执行时会进行数据访问     ,此时这些渲染函数被收集进入副作用函数中     ,建立数据 -> 副作用的映射关系。当数据变更时               ,再触发副作用函数的重新执行          ,即重新渲染               。

另一方面则在编译器中做了大量的静态优化     ,得益于这些优化               ,才让我们可以 易学易用的写出性能出色的 Vue 项目               。 下面简单介绍几种编译时优化策略:

1. 靶向更新

假设有以下模板:

<template> <p>hello world</p> <p>{{ msg }}</p> </template>>

其中一个 p 标签的节点是一个静态的节点          ,第二个 p 标签的节点是一个动态的节点,如果当 msg 的值发生了变化               ,那么理论上肉眼可见最优的更新方案应该是只做第二个动态节点的 diff 而无需进行第一个 p 标签节点的 diff。

上述模版转成 vnode 后的结果大致为:

const vnode = { type: Symbol(Fragment), children: [ { type: p, children: hello world }, { type: p, children: ctx.msg, patchFlag: 1 /* 动态的 text */ }, ], dynamicChildren: [ { type: p, children: ctx.msg, patchFlag: 1 /* 动态的 text */ }, ] }

此时组件内存在了一个静态的节点 <p>hello world</p>               ,在传统的 diff 算法里,还是需要对该静态节点进行不必要的 diff          。

而 Vue3 则是先通过 patchFlag 来标记动态节点 <p>{{ msg }}</p> 然后配合 dynamicChildren 将动态节点进行收集          ,从而完成在 diff 阶段只做靶向更新的目的               。

2. 静态提升

接下来               ,我们再来说一下     ,为什么要做静态提升呢? 如下模板所示:

<div> <p>text</p> </div>

在没有被提升的情况下其渲染函数相当于:

import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", null, [ _createElementVNode("p", null, "text") ])) }

很明显          ,p标签是静态的               ,它不会改变     。但是如上渲染函数的问题也很明显     ,如果组件内存在动态的内容     ,当渲染函数重新执行时               ,即使p标签是静态的          ,那么它对应的VNode也会重新创建          。

所谓的 “静态提升          ”     ,就是将一些静态的节点或属性提升到渲染函数之外               。如下面的代码所示:

import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "text", -1 /* HOISTED */) const _hoisted_2 = [ _hoisted_1 ] export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", null, _hoisted_2)) }

这就实现了减少VNode创建的性能消耗     。

而这里的静态提升步骤生成的 hoists               ,会在 codegenNode 会在生成代码阶段帮助我们生成静态提升的相关代码     。

预字符串化

Vue 3 在编译时会进行静态提升节点的 预字符串化               。什么是预字符串化呢?一起来看个示例:

<template> <p></p> ... 共 20+ 节点 <p></p> </template>

对于这样有大量静态提升的模版场景          ,如果不考虑 预字符串化 那么生成的渲染函数将会包含大量的 createElementVNode 函数:假设如上模板中有大量连续的静态的 p 标签,此时渲染函数生成的结果如下:

const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, null, -1 /* HOISTED */) // ... const _hoisted_20 = /*#__PURE__*/_createElementVNode("p", null, null, -1 /* HOISTED */) const _hoisted_21 = [ _hoisted_1, // ... _hoisted_20, ] export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", null, _hoisted_21)) }

createElementVNode 大量连续性创建 vnode 也是挺影响性能的               ,所以可以通过 预字符串化 来一次性创建这些静态节点               ,采用 与字符串化 后,生成的渲染函数如下:

const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<p></p>...<p></p>", 20) const _hoisted_21 = [ _hoisted_1 ] export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", null, _hoisted_21)) }

这样一方面降低了 createElementVNode 连续创建带来的性能损耗          ,也侧面减少了代码体积          。

小结

本小节为大家解读了部分 Vue 3 性能优化的设计               ,更多的内容可以参考作者写的小册:《Vue 3 技术揭秘》     。

接下来的文章将继续为大家解读 Vue 3 响应式设计原理和异步调度更新策略               。

推广自己的小册

如果你对 Vue 3 感兴趣     ,想去深耕一下 Vue 3 相关的设计理念          ,但是直接去啃 Vue 3 源码会非常晦涩难懂               ,比如一个 baseCreateRenderer 函数就有接近 2000 行代码     ,可能会让你半途而废          。

作者花了 3 个多月的时间呕心沥血的写了一个小册《Vue 3 技术揭秘》将会为您从头到尾的介绍 Vue 3 的优秀设计!

小册一方面会对 Vue 3 核心源码做适量的精简     ,让你可以只用关注核心逻辑实现;另一方面               ,也配了大量的插图          ,一图胜千言     ,可以更加生动地向你展示源码的运行机制。

本小册主要划分为了 5 大模块 来依次为你揭开 Vue 3 的“神秘面纱               ”               。

模块一:渲染器实现原理               。从根组件初始化开始               ,一步步介绍组件实例化          、完整更新               、diff 过程等。

模块二:响应式原理          。核心介绍 Vue 3 基于 Proxy 实现的响应式原理          ,深入解读依赖收集过程     、响应式触达过程和相关联的 watch     、computed               、inject/provide 函数实现以及异步批量更新原理               。在学习的过程中,你会渐进式体会到与 Vue 2 响应式原理的差异以及异步批量更新的不同之处     。

模块三:编译器实现原理          。重点讲解模板是如何被一步步编译成渲染函数的               ,以及在编译时 Vue 3 所做的大量编译时优化的工作               。

模块四:内置组件实现原理     。主要介绍 Vue 3 几个常用的内置组件:Transition          、KeepAlive     、Teleport                、Suspense 相关的组件运行机制和实现原理     。

模块五:特殊元素&指令               。重点分析 v-model 是如何实现双向数据绑定的               ,以及 slot 插槽是如何实现内容分发的          。

为方便你理解,我整理出来了如下的思维导图:

相信掌握了本小册这些模块的核心原理之后          ,你再去阅读 Vue 3 源码或者是解决 Vue 3 的疑难杂症时               ,会更加得心应手     。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
老铁到底是什么意思(【K哥爬虫普法】老铁需要车牌靓号吗?判刑的那种) win10应用商店无法打开这个应用(Win10系统打不开应用商店提示0x00000194错误代码怎么解决?)