vue3编译做了哪些优化
vue3编译优化有:1、引入了 patchFlag,用来标记动态内容;在编译过程中会根据不同的属性类型打上不同的标识,从而实现了快速diff算法。2、Block Tree。3、静态提升,是将静态的节点或者属性提升出去。4、预解析字符串化,当连续静态节点超过10个时,会将静态节点序列化为字符串。5、函数缓存;开启cacheHandlers选项后,函数会被缓存起来,后续可直接使用。
本教程操作环境:windows7系统、vue3版,DELL G3电脑。
本文主要来分析 Vue3.0
编译阶段做的优化,在 patch
阶段是如何利用这些优化策略来减少比对次数。
由于组件更新时依然需要遍历该组件的整个 vnode
树,比如下面这个模板:
<template> <div id="container"> <p class="text">static text</p> <p class="text">static text</p> <p class="text">{{ message }}</p> <p class="text">static text</p> <p class="text">static text</p> </div> </template>
整个 diff 过程如图所示:
可以看到,因为这段代码中只有一个动态节点,所以这里有很多 diff 和遍历其实都是不需要的,这就会导致 vnode 的性能跟模版大小正相关,跟动态节点的数量无关,当一些组件的整个模版内只有少量动态节点时,这些遍历都是性能的浪费。对于上述例子,理想状态只需要 diff 这个绑定 message 动态节点的 p 标签即可。
Vue.js 3.0
通过编译阶段对静态模板的分析,编译生成了 Block tree
。
Block tree
是一个将模板基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的,而且每个区块只需要以一个 Array
来追踪自身包含的动态节点。借助 Block tree
,Vue.js 将 vnode 更新性能由与模版整体大小相关提升为与动态内容的数量相关,这是一个非常大的性能突破。
PatchFlag
由于 diff
算法无法避免新旧虚拟 DOM
中无用的比较操作,Vue.js 3.0
引入了 patchFlag
,用来标记动态内容。在编译过程中会根据不同的属性类型打上不同的标识,从而实现了快速 diff
算法。PatchFlags
的所有枚举类型如下所示:
export const enum PatchFlags { TEXT = 1, // 动态文本节点 CLASS = 1 << 1, // 动态class STYLE = 1 << 2, // 动态style PROPS = 1 << 3, // 除了class、style动态属性 FULL_PROPS = 1 << 4, // 有key,需要完整diff HYDRATE_EVENTS = 1 << 5, // 挂载过事件的 STABLE_FRAGMENT = 1 << 6, // 稳定序列,子节点顺序不会发生变化 KEYED_FRAGMENT = 1 << 7, // 子节点有key的fragment UNKEYED_FRAGMENT = 1 << 8, // 子节点没有key的fragment NEED_PATCH = 1 << 9, // 进行非props比较, ref比较 DYNAMIC_SLOTS = 1 << 10, // 动态插槽 DEV_ROOT_FRAGMENT = 1 << 11, HOISTED = -1, // 表示静态节点,内容变化,不比较儿子 BAIL = -2 // 表示diff算法应该结束 }
Block Tree
左侧的 template
经过编译后会生成右侧的 render
函数,里面有 _openBlock
、_createElementBlock
、_toDisplayString
、_createElementVNode
(createVnode
) 等辅助函数。
let currentBlock = null function _openBlock() { currentBlock = [] // 用一个数组来收集多个动态节点 } function _createElementBlock(type, props, children, patchFlag) { return setupBlock(createVnode(type, props, children, patchFlag)); } export function createVnode(type, props, children = null, patchFlag = 0) { const vnode = { type, props, children, el: null, // 虚拟节点上对应的真实节点,后续diff算法 key: props?.["key"], __v_isVnode: true, shapeFlag, patchFlag }; ... if (currentBlock && vnode.patchFlag > 0) { currentBlock.push(vnode); } return vnode; } function setupBlock(vnode) { vnode.dynamicChildren = currentBlock; currentBlock = null; return vnode; } function _toDisplayString(val) { return isString(val) ? val : val == null ? "" : isObject(val) ? JSON.stringify(val) : String(val); }
此时生成的 vnode 如下:
此时生成的虚拟节点多出一个 dynamicChildren
属性,里面收集了动态节点 span
。
节点 diff 优化策略:
我们之前分析过,在 patch
阶段更新节点元素的时候,会执行 patchElement
函数,我们再来回顾一下它的实现:
const patchElement = (n1, n2) => { // 先复用节点、在比较属性、在比较儿子 let el = n2.el = n1.el; let oldProps = n1.props || {}; // 对象 let newProps = n2.props || {}; // 对象 patchProps(oldProps, newProps, el); if (n2.dynamicChildren) { // 只比较动态元素 patchBlockChildren(n1, n2); } else { patchChildren(n1, n2, el); // 全量 diff } }
我们在前面组件更新的章节分析过这个流程,在分析子节点更新的部分,当时并没有考虑到优化的场景,所以只分析了全量比对更新的场景。
而实际上,如果这个 vnode
是一个 Block vnode
,那么我们不用去通过 patchChildren
全量比对,只需要通过 patchBlockChildren
去比对并更新 Block
中的动态子节点即可。
由此可以看出性能被大幅度提升,从 tree
级别的比对,变成了线性结构比对。
我们来看一下它的实现:
const patchBlockChildren = (n1, n2) => { for (let i = 0; i < n2.dynamicChildren.length; i++) { patchElement(n1.dynamicChildren[i], n2.dynamicChildren[i]) } }
属性 diff 优化策略:
接下来我们看一下属性比对的优化策略:
const patchElement = (n1, n2) => { // 先复用节点、在比较属性、在比较儿子 let el = n2.el = n1.el; let oldProps = n1.props || {}; // 对象 let newProps = n2.props || {}; // 对象 let { patchFlag, dynamicChildren } = n2 if (patchFlag > 0) { if (patchFlag & PatchFlags.FULL_PROPS) { // 对所 props 都进行比较更新 patchProps(el, n2, oldProps, newProps, ...) } else { // 存在动态 class 属性时 if (patchFlag & PatchFlags.CLASS) { if (oldProps.class !== newProps.class) { hostPatchProp(el, 'class', null, newProps.class, ...) } } // 存在动态 style 属性时 if (patchFlag & PatchFlags.STYLE) { hostPatchProp(el, 'style', oldProps.style, newProps.style, ...) } // 针对除了 style、class 的 props if (patchFlag & PatchFlags.PROPS) { const propsToUpdate = n2.dynamicProps! for (let i = 0; i < propsToUpdate.length; i++) { const key = propsToUpdate[i] const prev = oldProps[key] const next = newProps[key] if (next !== prev) { hostPatchProp(el, key, prev, next, ...) } } } if (patchFlag & PatchFlags.TEXT) { // 存在动态文本 if (n1.children !== n2.children) { hostSetElementText(el, n2.children as string) } } } else if (dynamicChildren == null) { patchProps(el, n2, oldProps, newProps, ...) } } } function hostPatchProp(el, key, prevValue, nextValue) { if (key === 'class') { // 更新 class patchClass(el, nextValue) } else if (key === 'style') { // 更新 style patchStyle(el, prevValue, nextValue) } else if (/^on[^a-z]/.test(key)) { // events addEventListener patchEvent(el, key, nextValue); } else { // 普通属性 el.setAttribute patchAttr(el, key, nextValue); } } function patchClass(el, nextValue) { if (nextValue == null) { el.removeAttribute('class'); // 如果不需要class直接移除 } else { el.className = nextValue } } function patchStyle(el, prevValue, nextValue = {}){ ... } function patchAttr(el, key, nextValue){ ... }
总结: vue3
会充分利用 patchFlag
和 dynamicChildren
做优化。如果确定只是某个局部的变动,比如 style
改变,那么只会调用 hostPatchProp
并传入对应的参数 style
做特定的更新(靶向更新);如果有 dynamicChildren
,会执行 patchBlockChildren
做对比更新,不会每次都对 props 和子节点进行全量的对比更新。图解如下:
静态提升
静态提升是将静态的节点或者属性提升出去,假设有以下模板:
<div> <span>hello</span> <span a=1 b=2>{{name}}</span> <a><span>{{age}}</span></a> </div>
编译生成的 render
函数如下:
export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", null, [ _createElementVNode("span", null, "hello"), _createElementVNode("span", { a: "1", b: "2" }, _toDisplayString(_ctx.name), 1 /* TEXT */), _createElementVNode("a", null, [ _createElementVNode("span", null, _toDisplayString(_ctx.age), 1 /* TEXT */) ]) ])) }
我们把模板编译成 render
函数是这个酱紫的,那么问题就是每次调用 render
函数都要重新创建虚拟节点。
开启静态提升 hoistStatic
选项后
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "hello", -1 /* HOISTED */) const _hoisted_2 = { a: "1", b: "2" } export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", null, [ _hoisted_1, _createElementVNode("span", _hoisted_2, _toDisplayString(_ctx.name), 1 /* TEXT */), _createElementVNode("a", null, [ _createElementVNode("span", null, _toDisplayString(_ctx.age), 1 /* TEXT */) ]) ])) }
预解析字符串化
静态提升的节点都是静态的,我们可以将提升出来的节点字符串化。 当连续静态节点超过 10
个时,会将静态节点序列化为字符串。
假如有如下模板:
<div> <span>static</span> <span>static</span> <span>static</span> <span>static</span> <span>static</span> <span>static</span> <span>static</span> <span>static</span> <span>static</span> <span>static</span> </div>
开启静态提升 hoistStatic
选项后
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span>", 10) const _hoisted_11 = [ _hoisted_1] export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", null, _hoisted_11)) }
函数缓存
假如有如下模板:
<div @click="event => v = event.target.value"></div>
编译后:
const _hoisted_1 = ["onClick"] export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", { onClick: event => _ctx.v = event.target.value }, null, 8 /* PROPS */, _hoisted_1)) }
每次调用 render
的时候要创建新函数,开启函数缓存 cacheHandlers
选项后,函数会被缓存起来,后续可以直接使用
export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", { onClick: _cache[0] || (_cache[0] = event => _ctx.v = event.target.value) })) }
总结
以上几点即为 Vuejs
在编译阶段做的优化,基于上面几点,Vuejs
在 patch
过程中极大地提高了性能。
以上是vue3编译做了哪些优化的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

基于无阻塞、事件驱动建立的Node服务,具有内存消耗低的优点,非常适合处理海量的网络请求。在海量请求的前提下,就需要考虑“内存控制”的相关问题了。 1. V8的垃圾回收机制与内存限制 Js由垃圾回收机

当今前端开发中,Vue.js 已经成为了一个非常流行的框架。随着 Vue.js 的不断发展,单元测试变得越来越重要。今天,我们将探讨如何在 Vue.js 3 中编写单元测试,并提供一些最佳实践和常见的问题及解决方案。

文件模块是对底层文件操作的封装,例如文件读写/打开关闭/删除添加等等 文件模块最大的特点就是所有的方法都提供的**同步**和**异步**两个版本,具有 sync 后缀的方法都是同步方法,没有的都是异

PHP与Vue:完美搭档的前端开发利器在当今互联网高速发展的时代,前端开发变得愈发重要。随着用户对网站和应用的体验要求越来越高,前端开发人员需要使用更加高效和灵活的工具来创建响应式和交互式的界面。PHP和Vue.js作为前端开发领域的两个重要技术,搭配起来可以称得上是完美的利器。本文将探讨PHP和Vue的结合,以及详细的代码示例,帮助读者更好地理解和应用这两

跨域是开发中经常会遇到的一个场景,也是面试中经常会讨论的一个问题。掌握常见的跨域解决方案及其背后的原理,不仅可以提高我们的开发效率,还能在面试中表现的更加

在前端开发面试中,常见问题涵盖广泛,包括HTML/CSS基础、JavaScript基础、框架和库、项目经验、算法和数据结构、性能优化、跨域请求、前端工程化、设计模式以及新技术和趋势。面试官的问题旨在评估候选人的技术技能、项目经验以及对行业趋势的理解。因此,应试者应充分准备这些方面,以展现自己的能力和专业知识。

最开始的时候 JS 只在浏览器端运行,对于 Unicode 编码的字符串容易处理,但是对于二进制和非 Unicode 编码的字符串处理困难。并且二进制是计算机最底层的数据格式,视频/音频/程序/网络包

随着互联网技术的发展,前端开发变得日益重要。尤其是移动端设备的普及,更需要高效、稳定、安全又易维护的前端开发技术。而作为一门快速发展的编程语言,Go语言已经被越来越多的开发者所使用。那么,使用Go语言进行前端开发行得通吗?接下来,本文将为你详细说明如何使用Go语言进行前端开发。先来看看为什么使用Go语言进行前端开发。很多人认为Go语言是一门
