ソースコードから、Vue2がデータとメソッドを直接取得できる理由がわかる

ソースコードから、Vue2がデータとメソッドを直接取得できる理由がわかる

1. 例: これはデータとメソッドを直接取得できます

例:

定数vm = 新しいVue({
    データ: {
        名前: 「私は若川です」
    },
    メソッド: {
        言う名前(){
            コンソールにログ出力します。
        }
    },
});
console.log(vm.name); // 私は Ruochuan ですconsole.log(vm.sayName()); // 私は Ruochuan です

こうすることで、私が Ruochuan であることを出力できます。好奇心旺盛な人は、なぜこれに直接アクセスできるのか疑問に思うでしょう。

では、なぜthis.xxx datamethods内のデータを取得できるのでしょうか?
自分で書いた関数を構築することで、 Vueと同様の効果を実現するにはどうすればよいでしょうか?

関数Person(オプション){

}

const p = 新しいPerson({
    データ: {
        名前: '若川'
    },
    メソッド: {
        言う名前(){
            コンソールにログ出力します。
        }
    }
});

コンソールにログ出力します。
// 未定義
コンソールにログ出力します。
// キャッチされない TypeError: p.sayName は関数ではありません

あなたなら、どうやってそれを達成しますか?これらの質問を参考に、Vue2 のソースコードをデバッグして学習してみましょう。

2. 環境を準備し、ソースコードをデバッグして詳細を確認します。

ローカルに新しいフォルダーexamplesと新しいファイルindex.html作成できます。
<body></body>に次の js を追加します。

<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<スクリプト>
    定数vm = 新しいVue({
        データ: {
            名前: 「私は若川です」
        },
        メソッド: {
            言う名前(){
                コンソールにログ出力します。
            }
        },
    });
    console.log(仮想マシン名);
    コンソールにログ出力します。
</スクリプト>

次に、 npm i -g http-serverグローバルにインストールしてサービスを開始します。

npm i -g http-server
CDの例
http サーバー。
// ポートが使用中の場合は、ポート http-server -p 8081 を指定することもできます。

この方法では、先ほど作成した的index.htmlページをhttp://localhost:8080/で開くことができます。

デバッグ: F12 でデバッグを開き、ソース パネルで、const vm = new Vue({ の例では、ブレークポイントを設定します。

ページを更新した後、F11 キーを押して関数に入ります。ブレークポイントは Vue コンストラクターに入ります。

2.1 Vue コンストラクタ

関数 Vue (オプション) {
    if (!(このインスタンス Vue)
    ){
        warn('Vue はコンストラクターなので、`new` キーワードで呼び出す必要があります');
    }
    this._init(オプション);
}
// 初期化 initMixin(Vue);
Vue.js のインスタンスを Vue.js にインポートします。
イベントミックスイン(Vue);
ライフサイクルミックスイン(Vue);
レンダリングミックスイン(Vue);

:if (!(this instanceof Vue)){}コンストラクターが new キーワードを使用して呼び出されるかどうかを決定することに注意してください。
一般的に言えば、私たちは通常これを書くことは考えません。
もちろん、ソース コード ライブラリを参照して、独自の関数内でnewを呼び出すこともできます。しかし、一般的にvueプロジェクトではnew Vue()は 1 回だけ必要なので、必須ではありません。
jQuery ソース コードは内部的に新しいため、ユーザーにとってnew構造はありません。

jQuery = function(セレクター、コンテキスト) {
  // 新しいオブジェクトを返します return new jQuery.fn.init( selector, context );
};

jQueryが頻繁に呼び出されるからです。
実際、 jQuerynewなります。これは new を使用しない場合と同じ効果があります。

デバッグ: this._init(options); にブレークポイントを設定し、F11 キーを押して関数に入ります。

2.2 _init初期化関数

_init関数に入ると、この関数は比較的長く、多くのことを行います。 datamethodsに関する実装はinitState(vm)関数にあると推測します。

// コードは削除されました function initMixin (Vue) {
    Vue.prototype._init = 関数 (オプション) {
      var vm = this;
      // uid
      vm._uid = uid$3++;

      // これを回避するためのフラグ
      vm._isVue = true;
      // マージオプション
      if (オプション && options._isComponent) {
        // 内部コンポーネントのインスタンス化を最適化
        // 動的オプションのマージは非常に遅く、
        // 内部コンポーネント オプションには特別な処理が必要です。
        initInternalComponent(vm、オプション);
      } それ以外 {
        vm.$options = mergeOptions(
          コンストラクタオプションを解決します(vm.constructor)、
          オプション || {},
          仮想
        );
      }

      // 本当の自分をさらけ出す
      vm._self を vm に追加します。
      ライフサイクルを初期化します(vm);
      イベントを初期化します(vm);
      initRender(vm);
      フックを呼び出します(vm、'beforeCreate')。
      initInjections(vm); // データ/プロパティの前にインジェクションを解決する
      // 初期化状態 initState(vm);
      initProvide(vm); // data/props の後に provide を解決する
      callHook(vm, 'created');
    };
}

デバッグ:次に、 initState(vm)関数にブレークポイントを設定します。F8 キーを押してこのブレークポイントに直接ジャンプし、F11 キーを押してinitState関数に入ります。

2.3 initState 初期化状態

関数名から判断すると、この関数の主な機能は次のとおりです。

  • propsの初期化
  • 初期化methods
  • 監視データ
  • computedを初期化する
  • watchを初期化する
関数 initState (vm) {
    vm._watchers = [];
    var opts = vm.$options;
    opts.props の場合、 initProps(vm, opts.props); }
    // 渡されるメソッド、初期化メソッド if (opts.methods) { initMethods(vm, opts.methods); }
    // データを入力し、データを初期化する
    if (opts.data) {
      initData(vm);
    } それ以外 {
      観察(vm._data = {}, true /* asRootData */);
    }
    opts.computed の場合、 initComputed(vm, opts.computed); }
    opts.watch の場合、opts.watch は nativeWatch と等しくなります。
      initWatch(vm, opts.watch);
    }
}

まず、初期化methodsと初期化dataに焦点を当てます。

デバッグ: initMethodsinitData(vm)にブレークポイントを設定します。 initMethods関数を読み取った後、直接 F8 キーを押して initData(vm) 関数に戻ることができます。 F11 キーを押し続け、最初にinitMethods関数を入力します。

2.4 initMethods 初期化メソッド

関数 initMethods (vm, メソッド) {
    var props = vm.$options.props;
    for (var key in メソッド) {
      {
        if (typeof methods[key] !== 'function') {
          警告(
            "メソッド \"" + key + "\" は、コンポーネント定義で型 \"" + (typeof methods[key]) + "\" を持ちます。" +
            「関数を正しく参照しましたか?」
            仮想
          );
        }
        もしprops && hasOwn(props, key) であれば
          警告(
            (「メソッド \"" + キー + "\" はすでにプロパティとして定義されています。」)
            仮想
          );
        }
        if ((キーがVM内にある) && isReserved(キー)) {
          警告(
            「メソッド \"" + キー + "\" は既存の Vue インスタンス メソッドと競合します。" +
            「_ または $ で始まるコンポーネント メソッドの定義は避けてください。」
          );
        }
      }
      vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
    }
}

initMethods関数には主にいくつかの判断があります。

  • methods内の各項目が関数であるかどうかを判別し、そうでない場合は警告します。
  • methods内の各項目がプロパティと競合するかどうかを判別します。競合する場合は警告します。
  • methods内の各項目が新しい Vue インスタンス vm にすでに存在するかどうか、およびメソッド名が予約済みの _ $ (通常は JS の内部変数識別子を参照) で始まるかどうかを判断します。存在する場合は警告します。

これらの判断とは別に、 initMethods関数は実際に渡されたmethodsオブジェクトをトラバースし、 bindバインディング関数を使用して、 new Vueのインスタンス オブジェクトである vm を指していることがわかります。

このため、 this を通じてmethods内の関数に直接アクセスできます。

マウスをbind変数に移動して Alt キーを押すと、関数が定義されている場所を確認できます。これは 218 行目です。ここをクリックしてジャンプし、bind の実装を確認してください。

2.4.1 bindは関数を返し、これが指すものを変更します

関数 polyfillBind (fn, ctx) {
    関数boundFn(a){
      var l = 引数の長さ;
      戻るl
        ? l > 1
          ? fn.apply(ctx, 引数)
          : fn.call(ctx, a)
        : fn.call(ctx)
    }

    境界Fn._length = fn.length;
    戻り値boundFn
}

関数nativeBind(fn, ctx) {
  fn.bind(ctx) を返す
}

var bind = Function.prototype.bind
  ? ネイティブバインド
  :ポリフィルバインド;

簡単に言えば、ネイティブbind機能をサポートしていない古いバージョンと互換性があります。一方で、メソッドの書き方やパラメータ数の判断、 callapplyを使った実装との相性など、パフォーマンスの問題もあると言われています。
callapplybindの使い方や実装に慣れていない場合、JS のcallメソッドとapplyメソッドをシミュレートして実装できますか?

デバッグ: initMethods 関数を読み取った後、F8 キーを押して、上記の initData(vm) 関数のブレークポイントに戻ります。

2.5 initData データの初期化

  • initData関数もいくつかの判断を行います。主な実施内容は以下のとおりです。
  • 後で使用するために、まず_dataに値を割り当てます。
  • 最終的に得られたdataオブジェクトではないため警告が出されます。
  • トラバースdata 。それぞれ次のようになります。
  • methodsと競合する場合は警告が発行されます。
  • propsと競合する場合は警告が発行されます。
  • これは内部のプライベート予約属性ではありません。 _dataへのプロキシとして機能します。
  • 最後に、 dataを監視し、応答性のあるものにします。
関数 initData (vm) {
    var data = vm.$options.data;
    data = vm._data = typeof data === 'function'
      ? getData(データ、vm)
      : データ || {};
    if (!isPlainObject(データ)) {
      データ = {};
      警告(
        'データ関数はオブジェクトを返す必要があります:\n' +
        'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        仮想
      );
    }
    // インスタンス上のプロキシデータ
    var keys = Object.keys(データ);
    var props = vm.$options.props;
    var メソッド = vm.$options.methods;
    var i = キーの長さ;
    (i--) {
      var キー = keys[i];
      {
        if (メソッド && hasOwn(メソッド、キー)) {
          警告(
            (「メソッド \"" + キー + "\" はすでにデータ プロパティとして定義されています。」)
            仮想
          );
        }
      }
      もしprops && hasOwn(props, key) であれば
        警告(
          「データ プロパティ \"" + キー + "\" はすでにプロパティとして宣言されています。」 +
          "代わりにプロパティのデフォルト値を使用してください。",
          仮想
        );
      } そうでなければ (!isReserved(key)) {
        プロキシ(vm、"_data"、キー);
      }
    }
    // データを観察する
    観察(データ、true /* asRootData */);
}

2.5.1 データ取得

関数の場合は、関数を呼び出して実行し、オブジェクトを取得します。

関数 getData (データ, vm) {
    // #7573 データゲッターを呼び出すときに依存関係コレクションを無効にする
    プッシュターゲット();
    試す {
      データを返します。call(vm, vm)
    } キャッチ (e) {
      handleError(e, vm, "データ()");
      戻る {}
    ついに
      ポップターゲット();
    }
}

2.5.2 プロキシ

実際、 Object.definePropertyはオブジェクトを定義するために使用されます。ここでの目的は、 this.xxxthis._data.xxx。

/**
   * 何も操作しません。
   * 無駄なトランスパイルコードを残さずに Flow を満足させるための引数のスタブ化
   * ...rest を使用 (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)。
   */
関数 noop (a, b, c) {}
var 共有プロパティ定義 = {
    列挙可能: true、
    設定可能: true、
    取得: いいえ、
    設定: なし
};

関数プロキシ(ターゲット、ソースキー、キー){
    sharedPropertyDefinition.get = 関数 proxyGetter() {
      this[sourceKey][key]を返す
    };
    sharedPropertyDefinition.set = 関数 proxySetter (val) {
      this[ソースキー][キー] = val;
    };
    Object.defineProperty(ターゲット、キー、共有プロパティ定義);
}

2.5.3 Object.definePropertyはオブジェクトのプロパティを定義します

  • Object.definePropertyは非常に重要な API です。複数のプロパティを定義するためのAPIもあります: Object.defineProperties(obj, props) (ES5)
  • Object.definePropertyにはいくつかの重要な知識ポイントが含まれており、面接でテストされることが多いです。
  • value – プロパティを取得しようとしたときに返される値。
  • writable - プロパティが書き込み可能かどうか。
  • enumerable - プロパティが for in ループ内で列挙されるかどうか。
  • configurable - プロパティを削除できるかどうか。
  • set() — このプロパティの更新操作によって呼び出される関数。
  • get() —プロパティの値を取得するために呼び出される関数。

2.6 記事に出てくるいくつかの関数を最終的に統一的に説明する

2.6.1 hasOwn オブジェクト自体が所有するプロパティですか?

デバッグ モードで、Alt キーを押しながらマウスをメソッド名の上に移動すると、関数が定義されている場所を確認できます。クリックするとジャンプします。
/**
   * オブジェクトにプロパティがあるかどうかを確認します。
   */
var hasOwnProperty = Object.prototype.hasOwnProperty;
関数hasOwn(obj,キー){
  hasOwnProperty.call(obj, key) を返します。
}

hasOwn({ a: undefined }, 'a') // 真
hasOwn({}, 'a') // 偽
hasOwn({}, 'hasOwnProperty') // 偽
hasOwn({}, 'toString') // 偽
// これはプロパティ自体のプロパティであり、プロトタイプ チェーンを通じて上方向に検索されるものではありません。

2.6.2 isReserved $と_で始まる内部プライベート予約文字列であるかどうか

/**
   * 文字列が $ または _ で始まっているかどうかを確認します
   */
関数isReserved(str) {
  var c = (str + '').charCodeAt(0);
  c === 0x24 || c === 0x5F を返す
}
isReserved('_data'); // 真
isReserved('$options'); // 真
isReserved('data'); // 偽
isReserved('options'); // false

3. 最後に、60行以上のコードで簡略化されたバージョンを実装します。

関数 noop (a, b, c) {}
var 共有プロパティ定義 = {
    列挙可能: true、
    設定可能: true、
    取得: いいえ、
    設定: なし
};
関数プロキシ(ターゲット、ソースキー、キー){
    sharedPropertyDefinition.get = 関数 proxyGetter() {
      this[sourceKey][key]を返す
    };
    sharedPropertyDefinition.set = 関数 proxySetter (val) {
      this[ソースキー][キー] = val;
    };
    Object.defineProperty(ターゲット、キー、共有プロパティ定義);
}
関数 initData(vm){
  定数データ = vm._data = vm.$options.data;
  const keys = Object.keys(データ);
  var i = キーの長さ;
  (i--) {
    var キー = keys[i];
    プロキシ(vm、'_data'、キー);
  }
}
関数 initMethods(vm, methods){
  for (var key in メソッド) {
    vm[key] = typeof methods[key] !== 'function' ? noop : methods[key].bind(vm);
  } 
}

関数Person(オプション){
  vm = this とします。
  vm.$options = オプション;
  var opts = vm.$options;
  if(opts.data){
    initData(vm);
  }
  if(opts.methods){
    initMethods(vm, opts.methods)
  }
}

const p = 新しいPerson({
    データ: {
        名前: '若川'
    },
    メソッド: {
        言う名前(){
            コンソールにログ出力します。
        }
    }
});

コンソールにログ出力します。
// 実装前: 未定義
// '若川'
コンソールにログ出力します。
// 実装前: キャッチされない TypeError: p.sayName は関数ではありません
// '若川'

4. 結論

この記事で取り上げる基礎知識は主に以下の通りです。

  • コンストラクタ
  • this
  • callbindapply
  • Object.defineProperty

この記事は、ソースコードを読む読者から寄せられた疑問に答えることを目的としています。Vue ソースコードをデバッグして答えを見つける方法について詳しく説明します。
記事の冒頭にある質問に答えます。
methods内の関数に this を介して直接アクセスする理由は、 methods内のメソッドが、bind を介して this をnew Vueインスタンス (vm) として指定するためです。
これを介して data 内のデータに直接アクセスする理由は、data 内のプロパティが最終的に新しい Vue インスタンス (vm) 上の _data オブジェクトに格納されるためですthis._data.xxx。にアクセスすると、 Object.definePropertyプロキシの後でthis.xxxにアクセスします。
Vue のこの設計の利点は、入手が簡単なことです。また、 propsmethods 、 data が競合しやすくなるという不便さもあります。
記事全体としては難しくありませんが、読者は自分でデバッグすることを強くお勧めします。デバッグしてみると、Vue のソースコードは想像していたほど難しくなく、部分的には理解できることがわかるでしょう。
インスピレーション:一般的なテクノロジー、フレームワーク、ライブラリを扱うときは、好奇心を持ち続け、内部の原則についてさらに考える必要があります。事実とその背後にある理由を知ることができる。あなたは多くの人をはるかに凌駕することができます。
テンプレート構文で this キーワードを省略できるのはなぜかと疑問に思うかもしれません。実際、 with は内部テンプレートをコンパイルするときに使用されます。もっとエネルギーのある読者はこの原則を探求することができます。

Vue2 this がデータとmethodsを直接取得できる理由を明らかにするソースコードに関するこの記事はこれで終わりです。Vue2 this がデータとメソッドを直接取得できる理由に関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • vue2.0 と DataTable プラグインを組み合わせてテーブルを動的に更新する方法の詳細な説明
  • vue3とvue2の利点の比較
  • Vue2.0/3.0 での provide と inject の使用例
  • Vue2.xは、ユーザーのログインと終了を実装するためにルーティングナビゲーションガードを設定します。
  • vue2.x の徹底研究 - h 関数の説明
  • Vue2.x の応答性の簡単な説明と例
  • Vue3とVue2の利点のまとめ
  • Vue2は応答性を提供するためにprovide injectを実装しています
  • レスポンシブ原則と Vue2.0/3.0 の違いについての簡単な分析
  • vue.config.js からプロジェクト最適化までの vue2.x 構成
  • 手書きの Vue2.0 データハイジャックの例
  • Vue2.x - アンチシェイクとスロットリングの使用例

<<:  事例を通してLinux NFSの仕組みを詳細に分析

>>:  複数のフィールドをグループ化するMySQLグループ

推薦する

CentOS7 インストール GUI インターフェースとリモート接続の実装

ブラウザ (Web ドライバー) ベースの Selenium テクノロジを使用してデータをクロールす...

JavaScript Domはカルーセルの原理と例を実装します

カルーセルを作りたい場合、まずその原理を理解する必要があります。画像を右から左にスライドさせるにはど...

Vue ユニットテストに推奨されるプラグインと使用例

目次フレーム最高レベルのエラー報告活発なコミュニティとチーム冗談モカ推奨プラグインVue テストライ...

M1 ProチップでVueプロジェクトを開始する方法

目次導入Homebrewをインストールするnvmをインストールするノードをインストールするインストー...

CentOS7でFTPサーバーを設定する方法

FTP は主にファイル転送に使用され、Linux では vsftpd で実装されるのが一般的です。F...

サブセットかどうかを判断するためのMySQLメソッドの手順

目次1. 問題2. 解決策オプション1:オプション2: 1. 問題この話は、エラーと脱落率を照会する...

Linux のインスタンスにパブリック IP アドレスを割り当てる方法

説明するこのインターフェースを呼び出すときは、次の点に注意する必要があります。パブリック IP アド...

バッチファイルを処理するLinuxの1行コマンドの詳細な説明

序文最良の方法は、あなたが思いつく最も速い方法ではないかもしれません。職場で一時的に使用するスクリプ...

jQueryはマウスドラッグ画像機能を実装します

この例では、jQuery を使用してマウス ドラッグ イメージ機能を実装します。まず、ラッパーを設定...

React Router で履歴リダイレクトを使用する方法

react-routerでは、コンポーネント内のジャンプは<Link>で使用できます。し...

Centos7のFirewalldファイアウォールの基本コマンドの詳細な説明

1. Linuxファイアウォールの基礎Linux ファイアウォール システムは主にネットワーク層で動...

Mysql は最大接続数を表示し、最大接続数を変更します

MySQL 最大接続数の表示と最大接続数の変更1. 最大接続数を確認する '%max_con...

Mysql Workbench クエリ mysql データベース メソッド

Mysql Workbench はオープンソースのデータベース クライアントです。このオープンソース...

FileZilla_Server:425 データ接続を開けない問題を解決する方法

FileZilla Serverをサーバーにインストールすると、425データ接続を開けない問題が発生...