目次
diff
以前に分析したところ、patch ステージでノード要素を更新すると、
静态提升
预解析字符串化
函数缓存
总结
ホームページ ウェブフロントエンド フロントエンドQ&A vue3 コンパイルではどのような最適化が行われましたか?

vue3 コンパイルではどのような最適化が行われましたか?

Dec 19, 2022 pm 06:05 PM
フロントエンド vue.js

vue3 コンパイルの最適化には次のものが含まれます: 1. 動的コンテンツをマークするために patchFlag が導入され、コンパイル プロセス中に、異なる属性タイプに従って異なるラベルがマークされるため、高速 diff アルゴリズムが実現します。 2. ブロックツリー。 3. 静的プロモーションとは、静的なノードまたは属性をプロモートすることです。 4. 文字列化の事前解析。連続する静的ノードが 10 個を超える場合、静的ノードは文字列にシリアル化されます。 5. 関数のキャッシュ。cacheHandlers オプションをオンにすると、関数がキャッシュされ、後で直接使用できるようになります。

vue3 コンパイルではどのような最適化が行われましたか?

#このチュートリアルの動作環境: 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>
ログイン後にコピー

全体の差分プロセスは図に示すとおりです。

vue3 コンパイルではどのような最適化が行われましたか?##ご覧のとおり、このコードには動的ノードが 1 つしかないため、実際には不必要な差分と走査が多数あります。これにより、

パフォーマンスが低下します。 vnode はテンプレート サイズに直接関係します。動的ノードの数は

とは関係ありません。一部のコンポーネントのテンプレート全体に動的ノードが少数しかない場合、これらは走査はパフォーマンスの無駄です。上記の例では、理想的には、バインドされたメッセージの動的ノードの p タグを比較するだけで済みます。

Vue.js 3.0

コンパイル段階での静的テンプレートの分析を通じて、ブロック ツリーがコンパイルおよび生成されます。

ブロック ツリー

は、動的ノード命令に基づいてテンプレートを切り取るネストされたブロックです。各ブロック内のノード構造は固定されており、各ブロックには Array が 1 つだけ必要です。それ自体に含まれる動的ノードを追跡します。 ブロック ツリーの助けを借りて、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算法应该结束
}
ログイン後にコピー
ブロック ツリー

左側 # #templatevue3 コンパイルではどのような最適化が行われましたか? コンパイル後、右側の

render

関数が生成されます。これには、_openBlock_createElementBlock_toDisplayString が含まれます。 、 _createElementVNode(createVnode) およびその他の補助関数。 <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?.[&quot;key&quot;], __v_isVnode: true, shapeFlag, patchFlag }; ... if (currentBlock &amp;&amp; vnode.patchFlag &gt; 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 ? &quot;&quot; : isObject(val) ? JSON.stringify(val) : String(val); }</pre><div class="contentsignin">ログイン後にコピー</div></div>この時点で生成される vnode は次のとおりです:

この時点で生成される仮想ノードには

dynamicChildrenvue3 コンパイルではどのような最適化が行われましたか? 属性がもう 1 つあります、動的ノード

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 の完全な比較を渡す必要はありません。必要なのは、完全な比較だけです。 to passpatchBlockChildren 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, &#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){
  ...
}
ログイン後にコピー
概要: vue3

は、最適化のために

patchFlag

dynamicChildren を最大限に活用します。 style の変更など、ローカルな変更のみがあると判断された場合は、hostPatchProp のみが呼び出され、対応するパラメータ style が渡されます。特定の更新を行うためのメソッド (Targeted update); dynamicChildren がある場合、patchBlockChildren が比較と更新のために実行され、 は実行されません。毎回、props と子ノードの完全な比較と更新。回路図は以下の通りです: ############<h2 id="strong-静态提升-strong"><strong>静态提升</strong></h2><p>静态提升是将静态的节点或者属性提升出去,假设有以下模板:</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>&lt;div&gt; &lt;span&gt;hello&lt;/span&gt; &lt;span a=1 b=2&gt;{{name}}&lt;/span&gt; &lt;a&gt;&lt;span&gt;{{age}}&lt;/span&gt;&lt;/a&gt; &lt;/div&gt;</pre><div class="contentsignin">ログイン後にコピー</div></div><p>编译生成的 <code>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 在编译阶段做的优化,基于上面几点,Vuejspatch 过程中极大地提高了性能。

【相关推荐:vuejs视频教程web前端开发

以上がvue3 コンパイルではどのような最適化が行われましたか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Nodeのメモリ制御に関する記事 Nodeのメモリ制御に関する記事 Apr 26, 2023 pm 05:37 PM

ノンブロッキングおよびイベント駆動に基づいて構築されたノード サービスには、メモリ消費量が少ないという利点があり、大量のネットワーク リクエストの処理に非常に適しています。大量のリクエストを前提として、「メモリ制御」に関する問題を考慮する必要があります。 1. V8 のガベージ コレクション メカニズムとメモリ制限 Js はガベージ コレクション マシンによって制御されます

PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ Mar 16, 2024 pm 12:09 PM

PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ 今日のインターネットの急速な発展の時代において、フロントエンド開発はますます重要になっています。 Web サイトやアプリケーションのエクスペリエンスに対するユーザーの要求がますます高まっているため、フロントエンド開発者は、より効率的で柔軟なツールを使用して、応答性の高いインタラクティブなインターフェイスを作成する必要があります。フロントエンド開発の分野における 2 つの重要なテクノロジーである PHP と Vue.js は、組み合わせることで完璧なツールと見なされます。この記事では、PHP と Vue の組み合わせと、読者がこれら 2 つをよりよく理解し、適用できるようにするための詳細なコード例について説明します。

クロスドメインの問題を解決するにはどうすればよいですか?一般的なソリューションの簡単な分析 クロスドメインの問題を解決するにはどうすればよいですか?一般的なソリューションの簡単な分析 Apr 25, 2023 pm 07:57 PM

クロスドメインは開発においてよく遭遇するシナリオであり、インタビューでもよく議論される問題でもあります。一般的なクロスドメイン ソリューションとその背後にある原則を習得すると、開発効率が向上するだけでなく、面接でのパフォーマンスも向上します。

フロントエンドの面接官からよく聞かれる質問 フロントエンドの面接官からよく聞かれる質問 Mar 19, 2024 pm 02:24 PM

フロントエンド開発のインタビューでは、HTML/CSS の基本、JavaScript の基本、フレームワークとライブラリ、プロジェクトの経験、アルゴリズムとデータ構造、パフォーマンスの最適化、クロスドメイン リクエスト、フロントエンド エンジニアリング、デザインパターン、新しいテクノロジーとトレンド。面接官の質問は、候補者の技術スキル、プロジェクトの経験、業界のトレンドの理解を評価するように設計されています。したがって、候補者はこれらの分野で自分の能力と専門知識を証明するために十分な準備をしておく必要があります。

フロントエンド開発に Go 言語を使用するにはどうすればよいですか? フロントエンド開発に Go 言語を使用するにはどうすればよいですか? Jun 10, 2023 pm 05:00 PM

インターネット技術の発展に伴い、フロントエンド開発の重要性がますます高まっています。特にモバイル デバイスの人気により、効率的で安定しており、安全で保守が容易なフロントエンド開発テクノロジーが必要です。 Go 言語は、急速に発展しているプログラミング言語として、ますます多くの開発者によって使用されています。では、フロントエンド開発に Go 言語を使用することは可能でしょうか?次に、この記事ではフロントエンド開発にGo言語を使用する方法を詳しく説明します。まずはフロントエンド開発にGo言語が使われる理由を見てみましょう。多くの人は Go 言語は

Django はフロントエンドですか、バックエンドですか?それをチェックしてください! Django はフロントエンドですか、バックエンドですか?それをチェックしてください! Jan 19, 2024 am 08:37 AM

Django は、迅速な開発とクリーンなメソッドを重視した Python で書かれた Web アプリケーション フレームワークです。 Django は Web フレームワークですが、Django がフロントエンドなのかバックエンドなのかという質問に答えるには、フロントエンドとバックエンドの概念を深く理解する必要があります。フロントエンドはユーザーが直接対話するインターフェイスを指し、バックエンドはサーバー側プログラムを指し、HTTP プロトコルを通じてデータと対話します。フロントエンドとバックエンドが分離されている場合、フロントエンドとバックエンドのプログラムをそれぞれ独立して開発して、ビジネス ロジックとインタラクティブ効果、およびデータ交換を実装できます。

C# 開発経験の共有: フロントエンドとバックエンドの共同開発スキル C# 開発経験の共有: フロントエンドとバックエンドの共同開発スキル Nov 23, 2023 am 10:13 AM

C# 開発者としての私たちの開発作業には、通常、フロントエンドとバックエンドの開発が含まれますが、テクノロジーが発展し、プロジェクトが複雑になるにつれて、フロントエンドとバックエンドの共同開発はますます重要かつ複雑になってきています。この記事では、C# 開発者が開発作業をより効率的に完了できるようにする、フロントエンドとバックエンドの共同開発テクニックをいくつか紹介します。インターフェイスの仕様を決定した後、フロントエンドとバックエンドの共同開発は API インターフェイスの相互作用から切り離せません。フロントエンドとバックエンドの共同開発をスムーズに進めるためには、適切なインターフェース仕様を定義することが最も重要です。インターフェイスの仕様にはインターフェイスの名前が含まれます

golang はフロントエンドとして使用できますか? golang はフロントエンドとして使用できますか? Jun 06, 2023 am 09:19 AM

Golang はフロントエンドとして使用できます。Golang は、フロントエンド アプリケーションなど、さまざまなタイプのアプリケーションの開発に使用できる非常に多用途なプログラミング言語です。Golang を使用してフロントエンドを作成することで、 JavaScript などの言語によって引き起こされる一連の問題、たとえば、型安全性の低さ、パフォーマンスの低下、コードの保守の困難などの問題です。

See all articles