What optimizations have been done in vue3 compilation?
vue3 compilation optimizations include: 1. The patchFlag is introduced to mark dynamic content; during the compilation process, different labels will be marked according to different attribute types, thus realizing a fast diff algorithm. 2. Block Tree. 3. Static promotion is to promote static nodes or attributes. 4. Pre-parsing stringification, when there are more than 10 consecutive static nodes, the static nodes will be serialized into strings. 5. Function caching; after turning on the cacheHandlers option, the function will be cached and can be used directly later.
The operating environment of this tutorial: windows7 system, vue3 version, DELL G3 computer.
This article mainly analyzes the optimization done in the Vue3.0
compilation stage, and how to use these optimization strategies to reduce the number of comparisons in the patch
stage.
Since the entire vnode
tree of the component still needs to be traversed when the component is updated, such as the following template:
<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>
The entire diff process is as shown in the figure:
As you can see, because there is only one dynamic node in this code, there are many diffs and traversals that are actually unnecessary. This will cause the performance of vnode to be directly related to the template size, which is directly related to the template size. The number of dynamic nodes has nothing to do with . When there are only a few dynamic nodes in the entire template of some components, these traversals are a waste of performance. For the above example, ideally you only need to diff the p tag of the bound message dynamic node.
Vue.js 3.0
Through the analysis of the static template during the compilation stage, Block tree
is compiled and generated.
Block tree
is a nested block that cuts the template based on dynamic node instructions. The node structure inside each block is fixed, and each block only needs one Array
to track the dynamic nodes contained in itself. With the help of Block tree
, Vue.js improves vnode update performance from being related to the overall size of the template to being related to the amount of dynamic content. This is a very big performance breakthrough.
PatchFlag
Since thediff algorithm cannot avoid useless comparison operations in the old and new virtual
DOM,
Vue. js 3.0 introduced
patchFlag to mark dynamic content. During the compilation process, different identifiers will be marked according to different attribute types, thus realizing a fast
diff algorithm. All enumeration types for
PatchFlags are as follows:
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
After compilation, the render
function on the right side will be generated, which contains _openBlock
, _createElementBlock
, _toDisplayString
, _createElementVNode
(createVnode
) and other auxiliary functions. <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>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);
}</pre><div class="contentsignin">Copy after login</div></div>
The vnode generated at this time is as follows:
The virtual node generated at this time has one more
attribute, which collects Dynamic node span
.
We have analyzed before that when updating node elements in the
patch stage, patchElement# will be executed ## function, let’s review its implementation:
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 } }
We analyzed this process in the previous chapter of component update. When analyzing the sub-node update part, we did not consider the optimization scenario at that time, so we only The scenario of full comparison and update was analyzed.
In fact, if this vnode
is aBlock vnode, then we don’t need to pass
patchChildren full comparison, we only need to pass
patchBlockChildren Just compare and update the dynamic child nodes in
Block.
It can be seen that the performance has been greatly improved, from
tree level comparison to linear structure comparison.
Let’s take a look at its implementation:
const patchBlockChildren = (n1, n2) => { for (let i = 0; i < n2.dynamicChildren.length; i++) { patchElement(n1.dynamicChildren[i], n2.dynamicChildren[i]) } }
Attribute diff optimization strategy:
Next let’s take a look at the optimization strategy for attribute comparison: 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){
...
}
Copy after login
Summary: 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
will make full use ofpatchFlag and
dynamicChildren for optimization. If it is determined that there is only a local change, such as a
style change, then
hostPatchProp will only be called and the corresponding parameters
style will be passed in to make a specific update (
Targeted update); if there is
dynamicChildren, patchBlockChildren will be executed for comparison and update,
will not perform a full comparison and update of props and child nodes every time. The diagram is as follows:
静态提升
静态提升是将静态的节点或者属性提升出去,假设有以下模板:
<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
过程中极大地提高了性能。
The above is the detailed content of What optimizations have been done in vue3 compilation?. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



The Node service built based on non-blocking and event-driven has the advantage of low memory consumption and is very suitable for handling massive network requests. Under the premise of massive requests, issues related to "memory control" need to be considered. 1. V8’s garbage collection mechanism and memory limitations Js is controlled by the garbage collection machine

PHP and Vue: a perfect pairing of front-end development tools. In today's era of rapid development of the Internet, front-end development has become increasingly important. As users have higher and higher requirements for the experience of websites and applications, front-end developers need to use more efficient and flexible tools to create responsive and interactive interfaces. As two important technologies in the field of front-end development, PHP and Vue.js can be regarded as perfect tools when paired together. This article will explore the combination of PHP and Vue, as well as detailed code examples to help readers better understand and apply these two

Cross-domain is a scenario often encountered in development, and it is also an issue often discussed in interviews. Mastering common cross-domain solutions and the principles behind them can not only improve our development efficiency, but also perform better in interviews.

In front-end development interviews, common questions cover a wide range of topics, including HTML/CSS basics, JavaScript basics, frameworks and libraries, project experience, algorithms and data structures, performance optimization, cross-domain requests, front-end engineering, design patterns, and new technologies and trends. . Interviewer questions are designed to assess the candidate's technical skills, project experience, and understanding of industry trends. Therefore, candidates should be fully prepared in these areas to demonstrate their abilities and expertise.

With the development of Internet technology, front-end development has become increasingly important. Especially the popularity of mobile devices requires front-end development technology that is efficient, stable, safe and easy to maintain. As a rapidly developing programming language, Go language has been used by more and more developers. So, is it feasible to use Go language for front-end development? Next, this article will explain in detail how to use Go language for front-end development. Let’s first take a look at why Go language is used for front-end development. Many people think that Go language is a

As a C# developer, our development work usually includes front-end and back-end development. As technology develops and the complexity of projects increases, the collaborative development of front-end and back-end has become more and more important and complex. This article will share some front-end and back-end collaborative development techniques to help C# developers complete development work more efficiently. After determining the interface specifications, collaborative development of the front-end and back-end is inseparable from the interaction of API interfaces. To ensure the smooth progress of front-end and back-end collaborative development, the most important thing is to define good interface specifications. Interface specification involves the name of the interface

Django is a web application framework written in Python that emphasizes rapid development and clean methods. Although Django is a web framework, to answer the question whether Django is a front-end or a back-end, you need to have a deep understanding of the concepts of front-end and back-end. The front end refers to the interface that users directly interact with, and the back end refers to server-side programs. They interact with data through the HTTP protocol. When the front-end and back-end are separated, the front-end and back-end programs can be developed independently to implement business logic and interactive effects respectively, and data exchange.

Golang can be used as a front-end. Golang is a very versatile programming language that can be used to develop different types of applications, including front-end applications. By using Golang to write the front-end, you can get rid of a series of problems caused by languages such as JavaScript. For example, problems such as poor type safety, low performance, and difficult to maintain code.
