vue3 컴파일 최적화에는 다음이 포함됩니다. 1. 컴파일 프로세스 중에 동적 콘텐츠를 표시하기 위해 patchFlag가 도입되었으며, 다양한 속성 유형에 따라 다양한 라벨이 표시되므로 빠른 비교 알고리즘이 실현됩니다. 2. 블록 트리. 3. 정적 승격은 정적 노드 또는 속성을 승격시키는 것입니다. 4. 문자열화를 사전 구문 분석하는 경우 연속 정적 노드가 10개보다 많으면 정적 노드가 문자열로 직렬화됩니다. 5. 함수 캐싱; 캐시핸들러 옵션을 켜면 함수가 캐시되어 나중에 직접 사용할 수 있습니다.
이 튜토리얼의 운영 환경: windows7 시스템, vue3 버전, DELL G3 컴퓨터.
이 글에서는 주로 Vue3.0
编译阶段做的优化,在 patch
단계에서 비교 횟수를 줄이기 위해 이러한 최적화 전략을 사용하는 방법을 분석합니다.
다음 템플릿과 같이 구성 요소가 업데이트될 때 구성 요소의 전체 vnode
트리를 계속 순회해야 하기 때문에: 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 更新性能由与模版整体大小相关提升为与动态内容的数量相关,这是一个非常大的性能突破。
由于 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算法应该结束 }
左侧的 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
。
我们之前分析过,在 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
const patchBlockChildren = (n1, n2) => { for (let i = 0; i < n2.dynamicChildren.length; i++) { patchElement(n1.dynamicChildren[i], n2.dynamicChildren[i]) } }
tree
级别的比对,变成了线性结构比对。我们来看一下它的实现:
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){ ... }
接下来我们看一下属性比对的优化策略:
<div> <span>hello</span> <span a=1 b=2>{{name}}</span> <a><span>{{age}}</span></a> </div>
总结: vue3
会充分利用 patchFlag
和 dynamicChildren
做优化。如果确定只是某个局部的变动,比如 style
改变,那么只会调用 hostPatchProp
并传入对应的参数 style
做特定的更新(靶向更新);如果有 dynamicChildren
,会执行 patchBlockChildren
전체 diff 프로세스는 그림에 표시된 대로입니다.
보실 수 있는 이유는 이 코드에는 동적 노드가 하나만 있으므로 여기에는 실제로 불필요한 많은 diff 및 순회가 있습니다. 이로 인해 vnode의 성능이 템플릿 크기와 직접 관련되며 동적 노드 수와는 아무런 관련이 없습니다. 노드 일부 구성 요소가 전체 템플릿에서 소수의 동적 노드만 포함하는 경우 이러한 순회는 성능 낭비입니다. 위 예의 경우 이상적으로는 바인딩된 메시지 동적 노드의 p 태그만 비교하면 됩니다.
Vue.js 3.0
컴파일 단계에서 정적 템플릿 분석을 통해 블록 트리
가 컴파일되고 생성됩니다. 🎜🎜블록 트리
는 동적 노드 명령을 기반으로 템플릿을 자르는 중첩 블록입니다. 각 블록 내부의 노드 구조는 고정되어 있으며 각 블록에는 추적하는 데 하나의 배열
만 필요합니다. 포함된 동적 노드. 블록 트리
의 도움으로 🎜Vue.js는 템플릿의 전체 크기와 관련된 것에서 동적 콘텐츠의 양과 관련된 것으로 vnode 업데이트 성능을 향상시킵니다. 이는 매우 큰 성능 혁신입니다. . 🎜diff
알고리즘은 이전 가상 DOM</code 및 새 가상 <code>DOM</code에서 쓸모없는 비교 작업을 피할 수 없기 때문에 >, < code>Vue.js 3.0
에서는 동적 콘텐츠를 표시하기 위해 patchFlag
를 도입했습니다. 컴파일 프로세스 중에 다양한 속성 유형에 따라 다양한 식별자가 표시되므로 빠른 diff
알고리즘이 실현됩니다. PatchFlags
의 모든 열거형 유형은 다음과 같습니다: 🎜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 */) ]) ])) }
템플릿
이 컴파일된 것입니다 그러면 오른쪽에 _openBlock
, _createElementBlock
, _toDisplayString
, render
함수가 생성됩니다. >_createElementVNode< /code>(createVnode
) 및 기타 보조 기능. 🎜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 */) ]) ])) }
span
을 수집하는 추가 dynamicChildren
속성이 있습니다. 🎜패치
단계에서 노드 요소를 업데이트할 때 patchElement
함수를 실행한 후 구현을 검토해 보겠습니다. 🎜<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>
vnode
가 Block vnode
라면 전체 비교를 위해 patchChildren
을 거칠 필요는 없습니다. patchBlockChildren
을 거쳐야 Block
의 동적 하위 노드를 비교하고 업데이트할 수 있습니다.
트리
수준 비교에서 선형 구조 비교까지 성능이 크게 향상되었음을 확인할 수 있습니다. 🎜🎜구현을 살펴보겠습니다.🎜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>
vue3
는 최적화를 위해 patchFlag
및 dynamicChildren
을 최대한 활용합니다. style
변경과 같은 로컬 변경만 있는 것으로 판단되면 hostPatchProp
만 호출되고 해당 매개변수 style
는 특정 업데이트를 수행하기 위해 전달됩니다(🎜Targeted update🎜). dynamicChildren
이 있으면 비교 및 업데이트를 위해 patchBlockChildren
이 실행됩니다. 매번 완전히 비교되고 업데이트되지는 않습니다🎜. 다이어그램은 다음과 같습니다: 🎜🎜🎜🎜静态提升是将静态的节点或者属性提升出去,假设有以下模板:
<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 중국어 웹사이트의 기타 관련 기사를 참조하세요!