Heim > Web-Frontend > View.js > Hauptteil

Was sind die Inhalte der vue3-Kompilierung und -Optimierung?

王林
Freigeben: 2023-05-17 17:50:16
nach vorne
1331 Leute haben es durchsucht

Vue3-Kompilierungsoptimierungen umfassen: 1. Das patchFlag wird eingeführt, um dynamische Inhalte zu markieren; während des Kompilierungsprozesses werden verschiedene Logos entsprechend verschiedenen Attributtypen markiert, um A zu erreichen schneller Diff-Algorithmus. 2. Blockbaum. 3. Bei der statischen Förderung werden statische Knoten oder Attribute gefördert. Wenn mehr als 10 statische Knoten in einer Reihe vorhanden sind, wird die Vorverarbeitung stringifiziert und zu einer kontinuierlichen Folge statischer Knoten zusammengeführt. Wenn die Option „cacheHandlers“ aktiviert ist, wird die Funktion zwischengespeichert, sodass sie später direkt aufgerufen werden kann.

In diesem Artikel wird hauptsächlich die in der Kompilierungsphase von Vue3.0 durchgeführte Optimierung analysiert und wie diese Optimierungsstrategien verwendet werden können, um die Kosten im zu senken patch Anzahl der Vergleiche. Da der gesamte vnode-Baum der Komponente weiterhin durchlaufen werden muss, wenn die Komponente aktualisiert wird, wie zum Beispiel die folgende Vorlage: 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>
Nach dem Login kopieren

整个 diff 过程如图所示:

Was sind die Inhalte der vue3-Kompilierung und -Optimierung?

可以看到,因为这段代码中只有一个动态节点,所以这里有很多 diff 和遍历其实都是不需要的,这就会导致 vnode 的性能跟模版大小正相关,跟动态节点的数量无关,当一些组件的整个模版内只有少量动态节点时,这些遍历都是性能的浪费。对于上述例子,理想状态只需要 diff 这个绑定 message 动态节点的 p 标签即可。

Vue.js 3.0 通过编译阶段对静态模板的分析,编译生成了 Block tree

Block tree 是一个将模板基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的,而且每个区块只需要以一个 Array 来追踪自身包含的动态节点。借助 Block treeVue.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算法应该结束
}
Nach dem Login kopieren

Block Tree

Was sind die Inhalte der vue3-Kompilierung und -Optimierung?

左侧的 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);
}
Nach dem Login kopieren

此时生成的 vnode 如下:

Was sind die Inhalte der vue3-Kompilierung und -Optimierung?

此时生成的虚拟节点多出一个 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
  }
}
Nach dem Login kopieren

我们在前面组件更新的章节分析过这个流程,在分析子节点更新的部分,当时并没有考虑到优化的场景,所以只分析了全量比对更新的场景。

而实际上,如果这个 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])
  }
}
Nach dem Login kopieren

属性 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, &#39;class&#39;, null, newProps.class, ...)
        }
      }
      // 存在动态 style 属性时
      if (patchFlag & PatchFlags.STYLE) {
        hostPatchProp(el, &#39;style&#39;, 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 === &#39;class&#39;) { // 更新 class 
    patchClass(el, nextValue)
  } else if (key === &#39;style&#39;) { // 更新 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(&#39;class&#39;); // 如果不需要class直接移除
  } else {
    el.className = nextValue
  }
}

function patchStyle(el, prevValue, nextValue = {}){
  ...
}

function patchAttr(el, key, nextValue){
  ...
}
Nach dem Login kopieren

总结: vue3 会充分利用 patchFlagdynamicChildren 做优化。如果确定只是某个局部的变动,比如 style 改变,那么只会调用 hostPatchProp 并传入对应的参数 style 做特定的更新(靶向更新);如果有 dynamicChildren,会执行 patchBlockChildren

<div>
  <span>hello</span> 
  <span a=1 b=2>{{name}}</span>
  <a><span>{{age}}</span></a>
</div>
Nach dem Login kopieren
Nach dem Login kopieren
Der gesamte Diff-Prozess ist wie im gezeigt Abbildung: #🎜🎜 #Was sind die Inhalte der vue3-Kompilierung und Optimierung

#🎜🎜 #Wie Sie sehen können, gibt es hier viele Unterschiede und Durchläufe, die eigentlich unnötig sind, da es in diesem Code nur einen dynamischen Knoten gibt. Dies wird

die Leistung von beeinträchtigen vnode steht in direktem Zusammenhang mit der Vorlagengröße und der dynamischen Knotennummer. Die Anzahl hat nichts mit Was sind die Inhalte der vue3-Kompilierung und -Optimierung? zu tun. Wenn in der gesamten Vorlage einiger Komponenten nur wenige dynamische Knoten vorhanden sind, sind diese Durchläufe eine Leistungsverschwendung . Für das obige Beispiel müssen Sie im Idealfall nur das p-Tag des gebundenen dynamischen Nachrichtenknotens unterscheiden.

Vue.js 3.0 Durch die Analyse statischer Vorlagen während der Kompilierungsphase wird Blockbaum kompiliert und generiert. Blockbaum ist ein verschachtelter Block, der die Vorlage basierend auf dynamischen Knotenanweisungen schneidet. Die Knotenstruktur innerhalb jedes Blocks ist festgelegt und jeder Block ist nur ein Array< /code> wird benötigt, um die darin enthaltenen dynamischen Knoten zu verfolgen. Mit Hilfe von <code>Blocktree verbessert

Vue.js die Vnode-Update-Leistung von der Beziehung zur Gesamtgröße der Vorlage zur Menge des dynamischen Inhalts großer Leistungsdurchbruch.

PatchFlag#🎜🎜##🎜🎜##🎜🎜#Da der diff-Algorithmus alte und neue virtuelle nicht vermeiden kann Nutzlose Vergleichsoperationen in DOM, Vue.js 3.0 führte patchFlag ein, um dynamische Inhalte zu markieren. Während des Kompilierungsprozesses werden unterschiedliche Bezeichner entsprechend unterschiedlichen Attributtypen markiert, wodurch ein schneller diff-Algorithmus realisiert wird. Alle Enum-Typen für PatchFlags lauten wie folgt: #🎜🎜#
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 */)
    ])
  ]))
}
Nach dem Login kopieren
Nach dem Login kopieren

#🎜🎜#Block Tree#🎜🎜##🎜🎜##🎜 🎜 #Was sind die Inhalte der Vue3-Kompilierung und -Optimierung#🎜🎜## 🎜 🎜#Nach der Kompilierung generiert die template auf der linken Seite die render-Funktion auf der rechten Seite, die _openBlock und _createElementBlock< enthält /code> , <code>_toDisplayString, _createElementVNode(createVnode) und andere Hilfsfunktionen. #🎜🎜#
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 */)
    ])
  ]))
}
Nach dem Login kopieren
Nach dem Login kopieren
#🎜🎜#Der zu diesem Zeitpunkt generierte Vnode lautet wie folgt: #🎜🎜##🎜🎜#Was sind die Inhalte der vue3-Kompilierung und -Optimierung#🎜🎜##🎜🎜#Der zu diesem Zeitpunkt generierte virtuelle Knoten verfügt über ein zusätzliches dynamicChildren-Attribut, das sammelt dynamische Knotenspan. #🎜🎜#

#🎜🎜#Node-Diff-Optimierungsstrategie: #🎜🎜#

#🎜🎜#Wir haben es bereits analysiert, im patch</ Code > Beim stufenweisen Aktualisieren von Knotenelementen wird die Funktion <code>patchElement ausgeführt: #🎜🎜#
<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>
Nach dem Login kopieren
Nach dem Login kopieren
#🎜🎜#Wir haben diesen Prozess im vorherigen Kapitel der Komponentenaktualisierung analysiert Bei der Analyse der Aktualisierung untergeordneter Knoten wurde das Optimierungsszenario zu diesem Zeitpunkt nicht berücksichtigt, sodass nur das Szenario des vollständigen Vergleichs und der vollständigen Aktualisierung analysiert wurde. #🎜🎜##🎜🎜#Wenn dieser vnode tatsächlich ein Block-vnode ist, müssen wir patchChildren nicht durchlaufen Vollständiger Vergleich: Verwenden Sie einfach patchBlockChildren, um die dynamischen untergeordneten Knoten in Block zu vergleichen und zu aktualisieren. Es ist ersichtlich, dass die Leistung vom Vergleich auf Baum-Ebene bis zum Vergleich der linearen Struktur erheblich verbessert wurde. #🎜🎜##🎜🎜# Werfen wir einen Blick auf die Implementierung: #🎜🎜#
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))
}
Nach dem Login kopieren
Nach dem Login kopieren

#🎜🎜#Attribute-Diff-Optimierungsstrategie: #🎜🎜#

#🎜🎜#Als nächstes werfen wir einen Blick auf die Optimierungsstrategie für den Attributvergleich: #🎜🎜#
<div @click="event => v = event.target.value"></div>
Nach dem Login kopieren
Nach dem Login kopieren
#🎜🎜#Zusammenfassung: vue3 wird patchFlag vollständig nutzen und dynamicChildren führt eine Optimierung durch. Wenn festgestellt wird, dass nur eine lokale Änderung vorliegt, beispielsweise eine style-Änderung, werden nur hostPatchProp und der entsprechende Parameter style aufgerufen übergeben werden, um ein bestimmtes Update durchzuführen (#🎜🎜#targeted update#🎜🎜#); Wenn dynamicChildren vorhanden ist, wird patchBlockChildren zum Vergleich und zur Aktualisierung ausgeführt. #🎜🎜# aktualisiert Requisiten nicht jedes Mal. Vollständiger Vergleich und Aktualisierung mit untergeordneten Knoten #🎜🎜#. Das Diagramm sieht wie folgt aus: #🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#statische Werbung#🎜🎜##🎜🎜##🎜🎜#Bei der statischen Werbung geht es darum, statisch umzuwandeln Knoten oder Attribute Aktualisieren Sie es, vorausgesetzt, Sie haben die folgende Vorlage: #🎜🎜#
<div>
  <span>hello</span> 
  <span a=1 b=2>{{name}}</span>
  <a><span>{{age}}</span></a>
</div>
Nach dem Login kopieren
Nach dem Login kopieren

编译生成的 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 */)
    ])
  ]))
}
Nach dem Login kopieren
Nach dem Login kopieren

我们把模板编译成 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 */)
    ])
  ]))
}
Nach dem Login kopieren
Nach dem Login kopieren

预解析字符串化

静态提升的节点都是静态的,我们可以将提升出来的节点字符串化。 当连续静态节点超过 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>
Nach dem Login kopieren
Nach dem Login kopieren

开启静态提升 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))
}
Nach dem Login kopieren
Nach dem Login kopieren

函数缓存

假如有如下模板:

<div @click="event => v = event.target.value"></div>
Nach dem Login kopieren
Nach dem Login kopieren

编译后:

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))
}
Nach dem Login kopieren

每次调用 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)
  }))
}
Nach dem Login kopieren

Das obige ist der detaillierte Inhalt vonWas sind die Inhalte der vue3-Kompilierung und -Optimierung?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:yisu.com
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage