Vue computedのキャッシュ実装原理の詳細な説明

Vue computedのキャッシュ実装原理の詳細な説明

この記事では、計算された初期化と更新のプロセスを説明し、計算されたプロパティがどのようにキャッシュされ、依存関係がどのように収集されるかを確認するために、次の例に焦点を当てます。

<div id="アプリ">
  <span @click="change">{{sum}}</span>
</div>
<script src="./vue2.6.js"></script>
<スクリプト>
  新しいVue({
    el: "#app",
    データ() {
      戻る {
        カウント: 1,
      }
    },
    メソッド: {
      変化() {
        このカウント = 2
      },
    },
    計算: {
      合計() {
        this.count + 1 を返す
      },
    },
  })
</スクリプト>

計算結果を初期化する

vueが初期化されると、まずinitメソッドが実行され、その中のinitStateが計算されたプロパティを初期化します。

opts.computed の場合、{initComputed(vm, opts.computed);}

以下はinitComputedのコードです

var ウォッチャー = vm._computedWatchers = Object.create(null); 
// 計算されたプロパティごとに計算されたウォッチャーを順番に定義します
for (const キー in 計算済み) {
  const userDef = computed[キー]
  watchers[キー] = 新しいウォッチャー(
      vm、// インスタンス ゲッター、// ユーザーが評価関数の合計に渡した値
      noop, // コールバック関数は最初に無視できます { lazy: true } // 計算されたウォッチャーをマークするために lazy 属性を宣言します
  )
  // ユーザーが this.sum を呼び出すと何が起こるか defineComputed(vm, key, userDef)
}

各計算プロパティに対応する計算ウォッチャーの初期状態は次のとおりです。

{
    依存関係: [],
    汚い: 本当、
    ゲッター: ƒ sum(),
    怠惰: 本当、
    値: 未定義
}

最初は値が undefined であり、lazy が true であることがわかります。これは、値が遅延計算され、テンプレートで実際に値が読み取られるまで計算されないことを意味します。

このダーティ属性は実際にはキャッシュの鍵となるので、まず覚えておいてください。

次に、ユーザーが計算プロパティ this.sum の値を読み取った後に何が起こるかを決定する、より重要な defineComputed を見てみましょう。引き続き、プロセスに影響しないロジックを簡素化して除外します。

Object.defineProperty(ターゲット、キー、{ 
    得る() {
        // 先ほど述べたコンポーネントインスタンスから計算されたウォッチャーを取得します
        const ウォッチャー = this._computedWatchers && this._computedWatchers[キー]
        if (ウォッチャー) {
          // ダーティの場合にのみ再評価されます if (watcher.dirty) {
            // これは評価し、getを呼び出し、Dep.targetを設定します
            ウォッチャー.評価()
          }
          // これも重要なポイントなので、後で詳しく説明します if (Dep.target) {
            ウォッチャー.depend()
          }
          //最後に計算された値を返します return watcher.value
        }
    }
})

この関数は、詳しく見る必要があります。いくつかのことを行います。初期化プロセスで説明しましょう。

まず、ダーティの概念はダーティなデータを表します。つまり、ユーザーが渡した合計関数を再度呼び出してデータを評価する必要があることを意味します。今のところ、更新ロジックは無視しましょう。テンプレートで {{sum}} が最初に読み取られるときは、必ず true になるはずなので、初期化は評価を経ることになります。

評価する() {
  //get関数を呼び出して評価します。this.value = this.get()
  // ダーティを false としてマークする
  this.dirty = 偽
}

この関数は実際には非常に明確で、最初に評価してから dirty を false に設定します。 Object.defineProperty のロジックを振り返ってみましょう。次回、特別な状況なしに sum が読み取られるときに、dirty が false の場合は、watcher.value の値を返すだけで済みます。これは、実際には計算プロパティ キャッシュの概念です。

依存関係の収集

初期化が完了するとレンダリングのために render が呼び出され、render 関数はウォッチャーのゲッターとして機能します。このとき、ウォッチャーはレンダリング ウォッチャーです。

更新コンポーネント = () => {
  vm._update(vm._render(), ハイドレーション)
}
// レンダリング ウォッチャーを作成します。レンダリング ウォッチャーが初期化されると、その get() メソッド、つまりレンダリング関数が呼び出され、依存関係が収集されます。new Watcher(vm, updateComponent, noop, {}, true /* isRenderWatcher */)

ウォッチャーのgetメソッドを見てみましょう

得る () {
    //現在のウォッチャーをスタックの一番上に置き、Dep.target に設定します
    pushTarget(これ)
    値を与える
    定数 vm = this.vm
    // ユーザー定義関数を呼び出すと this.count にアクセスし、そのゲッターメソッドにアクセスします。これについては後述します。 value = this.getter.call(vm, vm)
    // 評価が完了したら、現在のウォッチャーがスタックからポップアウトされます popTarget()
    this.cleanupDeps()
    戻り値
 }

レンダリング ウォッチャーのゲッターが実行されると (レンダリング関数)、this.sum にアクセスし、計算属性のゲッター、つまり initComputed で定義されたメソッドがトリガーされます。sum にバインドされた計算ウォッチャーを取得した後、初期化中に dirty が true であるため、その assess メソッドが呼び出され、最後に get() メソッドが呼び出されて、計算ウォッチャーがスタックの一番上に配置されます。このとき、Dep.target も計算ウォッチャーです。

次に、get メソッドを呼び出すと this.count にアクセスし、count 属性のゲッターがトリガーされ (以下に示すように)、現在の Dep.target に格納されているウォッチャーが count 属性に対応する dep に収集されます。この時点で評価は終了し、ウォッチャーをスタックからポップするために popTarget() が呼び出されます。この時点で、以前のレンダリング ウォッチャーがスタックの一番上にあり、Dep.target が再びレンダリング ウォッチャーになります。

// クロージャでは、キーカウントに定義された依存関係が保持されます
定数 dep = 新しい Dep()
 
// クロージャは最後のset関数によって設定された値も保持します
valを
 
Object.defineProperty(obj, キー, {
  取得: 関数reactiveGetter() {
    定数値 = val
    // Dep.target は現在ウォッチャーを計算しています
    if (依存ターゲット) {
      // 依存関係を収集 dep.depend()
    }
    戻り値
  },
})
// 依存()
依存する() {
  if (依存ターゲット) {
    Dep.target.addDep(これ)
  }
}
// ウォッチャーの addDep 関数 addDep (dep: Dep) {
  // ここでは、簡素化のために一連の重複排除操作が実行されます // ここでは、count の dep も独自の deps に格納されます this.deps.push(dep)
  // ウォッチャー自体をパラメータとして // dep の addSub 関数に戻る dep.addSub(this)
}
クラス Dep {
  サブ = []
 
  addSub (サブ: ウォッチャー) {
    this.subs.push(サブ)
  }
}

これら 2 つのコードを通じて、計算されたウォッチャーは属性バインド dep によって収集されます。 Watcher は dep に依存し、dep も watcher に依存します。この相互依存データ構造により、watcher がどの dep に依存し、dep がどの watcher に依存しているかを簡単に知ることができます。

次にwatcher.depend()を実行します。

// ウォッチャー依存
依存する() {
  i = this.deps.length とします
  (i--) {
    this.deps[i].depend()
  }
}

先ほどのウォッチャーフォームの計算を覚えていますか?その deps には count の dep が格納されます。つまり、countのdep.depend()が再度呼び出されることになります。

クラス Dep {
  サブ = []
  
  依存する() {
    if (依存ターゲット) {
      Dep.target.addDep(これ)
    }
  }
}

今回は Dep.target がすでにレンダリング ウォッチャーであるため、このカウントの dep はレンダリング ウォッチャーを独自のサブに格納します。

最後に、count の依存関係が収集され、その dep は次のようになります。

{
    サブ: [合計計算ウォッチャー、レンダリングウォッチャー]
}

アップデートを配布する

ここで、この質問の重要なポイントに移ります。カウントが更新されたら、ビューの更新をどのようにトリガーするのでしょうか?

count のレスポンシブ ハイジャック ロジックに戻りましょう。

// クロージャでは、キーカウントに定義された依存関係が保持されます
定数 dep = 新しい Dep()
 
// クロージャは最後のset関数によって設定された値も保持します
valを
 
Object.defineProperty(obj, キー, {
  設定: 関数 reactiveSetter (newVal) {
      val = 新しい値
      // カウントの依存関係の通知をトリガーします
      dep.notify()
    }
  })
})

さて、ここで、先ほど慎重に準備した count の dep の通知関数がトリガーされます。

クラス Dep {
  サブ = []
  
  通知(){
    (i = 0, l = subs.length; i < l; i++) の場合 {
      subs[i].update()
    }
  }
}

ここでのロジックは非常に単純です。サブに保存されたウォッチャーの更新メソッドを順番に呼び出します。つまり、

  1. ウォッチャーの更新を計算するために呼び出す
  2. レンダリングウォッチャーの更新を呼び出す

ウォッチャーの更新の計算

アップデート () {
  if (this.lazy) {
    this.dirty = true
  }
}

計算ウォッチャーの dirty プロパティを true に設定し、次の読み取りを静かに待つだけです (render 関数が再度実行されると、sum プロパティに再度アクセスされますが、このとき dirty は true になっているため、再度評価されます)。

レンダリングウォッチャーの更新

ここでは実際に vm._update(vm._render()) 関数を呼び出して、レンダリング関数によって生成された vnode に従ってビューを再レンダリングします。
レンダリング プロセスでは、su の値にアクセスされるため、sum で定義された get に戻ります。

Object.defineProperty(ターゲット、キー、{ 
    得る() {
        const ウォッチャー = this._computedWatchers && this._computedWatchers[キー]
        if (ウォッチャー) {
          // 前のステップで、dirty はすでに true に設定されていたため、if (watcher.dirty) { で再評価されます。
            ウォッチャー.評価()
          }
          if (依存ターゲット) {
            ウォッチャー.depend()
          }
          //最後に計算された値を返します return watcher.value
        }
    }
})

前の手順でのレスポンシブ プロパティの更新により、計算されたウォッチャーのダーティ アップデートが true にトリガーされます。そのため、ユーザーが渡した sum 関数が再度呼び出され、最新の値が計算され、当然ながら最新の値がページに表示されます。

これまでに、属性更新を計算するプロセス全体が終了しました。

総括する

  1. データと計算を初期化し、それぞれ set メソッドと get メソッドをプロキシし、データ内のすべてのプロパティに対して一意の dep インスタンスを生成します。
  2. 計算された合計の一意のウォッチャーを生成し、vm._computedWatchersに保存します。
  3. レンダリング関数が実行されると、sum 属性にアクセスし、initComputed で定義された getter メソッドを実行し、Dep.target を sum のウォッチャーにポイントして、属性の特定のメソッド sum を呼び出します。
  4. sum メソッドで this.count にアクセスすると、this.count プロキシの get メソッドが呼び出され、this.count の dep が sum のウォッチャーに追加され、dep 内のサブがこのウォッチャーに追加されます。
  5. vm.count = 2 に設定し、count エージェントの set メソッドを呼び出して、dep の notification メソッドをトリガーします。これは計算プロパティであるため、ウォッチャー内の dirty のみが true に設定されます。
  6. 最後のステップでは、vm.sum が get メソッドにアクセスすると、sum の watcher.dirty が true であることがわかり、新しい値を取得するために watcher.evaluate() メソッドが呼び出されます。

以上がvue computedのキャッシュ実装原理の詳細な説明です。vue computedのキャッシュ実装の詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Vue - 計算プロパティ「****」が割り当てられましたが、セッターがありませんというエラーの解決。
  • Vueのcomputedとwatchの違いは何ですか?
  • Vue の watch と computed の詳細な説明
  • Vue における watch と computed の違いと使い方
  • Vue における computed と watch の違い
  • Vue における watch、computed、updated の違いと使い方
  • Vueの計算プロパティとウォッチの違いを簡単に理解する
  • Vue 計算プロパティのコード例
  • Vue のデータ、計算、ウォッチのソースコードに関する簡単な説明
  • Vueで監視と計算がデータの変更とその違いを監視できるのはなぜですか
  • Vue における filter と computed の違いと使い方

<<:  mysql5.7.19 winx64 解凍版のインストールと設定のチュートリアル

>>:  Docker プライベートリポジトリの管理とローカルリポジトリ内のイメージの削除

推薦する

IDEA で Docker プロジェクトをデプロイする手順

現在、ほとんどのプロジェクトが Docker 上にデプロイされ始めていますが、デプロイのプロセスはま...

MySQLで重複行を削除する方法

SQL文 /* MySQL で重複行を削除するいくつかの方法 ---Chu Minfei ---20...

なぜCSSをヘッドタグに配置する必要があるのか

考えてみてください。なぜcss 、 javascriptのようにbodyタグの末尾ではなく、 hea...

Linux システムで httpd の自動インストールと構成を Ansible で実装する方法

1. Ansibleのプレイブックを使用してhttpdを自動的にインストールする1) まず、Ansi...

Virtualbox で Ubuntu 16.04 の起動時に共有ディレクトリを自動的にマウントする最良の方法

仮想マシンを使用する人は通常、操作と使用を容易にするために仮想マシン用の共有ディレクトリを設定します...

画像を使用してハイパーリンクのパーソナライズされた下線を実現します

画像内に下線付きのリンクが表示されても驚かないでください。実はとても簡単なので、あなたにもできるので...

製品を選択した後、右下隅に√記号を表示するための純粋なCSS

おすすめの記事: CSS 疑似クラスの右下隅をクリックすると、選択を示すチェックマークが表示されます...

CSS3 はアニメーション属性を使用してクールな効果を実現します (推奨)

animation-name アニメーション名。複数のアニメーションがバインドされていることを示す...

HTML テーブルタグチュートリアル (19): 行タグ

<TR> タグの属性は、次の表に示すように、テーブル内の各行のプロパティを設定するために...

nginx を使用したプロキシ サーバーの設定

Nginx は、リバース プロキシ機能を使用して負荷分散を実装できるほか、フォワード プロキシ機能を...

MySQL マスタースレーブ同期の原理と応用

目次1. マスタースレーブ同期原理マスタースレーブ同期アーキテクチャ図(非同期同期)マスタースレーブ...

MySQL でスロークエリを有効にする方法の例

序文スロー クエリ ログは、MySQL で非常に重要な機能です。MySQL のスロー クエリ ログ機...

fullpage.js フルスクリーンスクロールの具体的な使い方

1.fullpage.js ダウンロードアドレスhttps://github.com/alvarot...

単一/複数行テキストを含む div を垂直方向に中央揃えする N 通りの方法 (高さ不明/高さ固定)

この問題について話すとき、垂直方向の中央揃えを設定するための vertical-align 属性が ...

Vue v-model 関連の知識のまとめ

​v-model は、入力とフォーム データ間、または 2 つのコンポーネント間の双方向データ バイ...