JavaScript で配列の変更を監視する方法

JavaScript で配列の変更を監視する方法

序文

以前、defineProperty を紹介したとき、オブジェクトの変更のみを監視でき、配列の変更は監視できないことを説明しました。

この記事では、配列の変更を監視する方法を説明します。

中心的なアイデア: 元の配列を変更する方法を見つけ、それらのメソッドを乗っ取る。

上記の文章は非常に重要です。先に進む前に必ず 3 回読んで覚えておいてください。

元の配列を変更するためによく使用されるメソッドには、push、pop、shift、unshift、reverse、sort、splice などがあります。

つまり、これらのメソッドは配列のエントリを変更します。

配列インスタンスと配列プロトタイプの間に新しいプロトタイプを追加する

Array.prototype を直接変更することは非常に危険です。

考え方を変えて、既存の配列プロトタイプをコピーし、その中のメソッドを変更しますが、ここではプロトタイプ上のメソッドは列挙可能ではないため、コピーできません。

では、考え方を変えてみましょう。配列と配列のプロトタイプの間にプロトタイプを挿入して、プロトタイプ チェーン (配列 => 新しいプロトタイプ => 配列のプロトタイプ) を形成します。次に、同じ名前のメソッドを新しいプロトタイプに追加できます。

まず擬似コードを使用して理解します。

// 疑似コード let arr = [];
arr.__proto__ = 新しいプロトタイプ;
newPrototype.__proto__ = Array.prototype;
// 次に、新しいプロトタイプに同じ名前のメソッドを追加できます。newPrototype.push = xxx;

実際のコードは以下のようになります。コアではObject.createを使用しています。

// Object.create は新しいオブジェクトを返します。新しいオブジェクトの __proto__ は渡されるパラメーターです。
newPrototype = Object.create(Array.prototype); を作成します。
// 次に、新しいプロトタイプに同じ名前のメソッドを追加できます。newPrototype.push = xxx;

// 監視する配列。新しいプロトタイプをバインドするだけです。let arr = [];
arr.__proto__ = 新しいプロトタイプ;

プッシュを例に挙げると、プッシュをハイジャックする

新しいプロトタイプのプッシュ メソッドを書き換えるだけで、古いプッシュを実行するだけでなく、他の操作も実行できます。

newPrototype = Object.create(Array.prototype); を作成します。

// 新しいプロトタイプに同じ名前のプッシュを追加します
newPrototype.push = function(...args) {
  // セマンティック this
  curArr = this とします。
  console.log("push が使用されています");
  //最後に、元のプッシュが実行されます
  Array.prototype.push.call(curArr, ...args) を返します。
};

// 監視する配列。新しいプロトタイプをバインドするだけです。let arr = [];
arr.__proto__ = 新しいプロトタイプ;

// pushが実行されると、arr.push(1)が出力されます。

他のメソッドも同様です。他のメソッドを書いてみてください

他の方法のハイジャック

他のメソッドもロジックが同じであり、直接走査できるため、一緒に記述されます。

newPrototype = Object.create(Array.prototype); を作成します。

メソッドを ["push"、"pop"、"shift"、"unshift"、"reverse"、"sort"、"splice"] とします。

メソッド.forEach(メソッド => {
  newPrototype[メソッド] = function(...args) {
    console.log(`${method} が使用されました`);
    Array.prototype[method].call(this, ...args) を返します。
  };
});

// 監視する配列。新しいプロトタイプをバインドするだけです。let arr = [];
arr.__proto__ = 新しいプロトタイプ;

// 実行するとarr.push(1)が出力されます。
arr.pop();

配列内に配列項目がある場合は、それらも監視する必要があります。

ここで配列内に配列が存在する場合、配列内の各項目を走査する必要があります。配列の場合でも、新しいプロトタイプを指す必要があります。

はい、再帰が使用されます。

newPrototype = Object.create(Array.prototype); を作成します。

メソッドを ["push"、"pop"、"shift"、"unshift"、"reverse"、"sort"、"splice"] とします。

メソッド.forEach(メソッド => {
  newPrototype[メソッド] = function(...args) {
    console.log(`${method} が使用されました`);
    Array.prototype[method].call(this, ...args) を返します。
  };
});

関数 observeArr(arr) {
  // これは条件付き制限と再帰終了条件の両方です if (!Array.isArray(arr)) {
    戻る;
  }
  // 配列全体が新しいプロトタイプを指します arr.__proto__ = newPrototype;
  // 配列内の各項目は、配列の場合は、新しいプロトタイプも指します。
  arr.forEach(Arr を観察します);
}
// 監視する配列。新しいプロトタイプをバインドするだけです。let arr = [[1, 2, 3]];
観測Arr(arr);

// 実行するとarr[0].push(1)が出力されます。
arr[1].pop();

配列に追加された新しい項目は、配列の場合は新しいプロトタイプを指す必要があります。

要素を追加できるメソッド: push unshift splice。

新しく追加された要素を見つけると、配列も新しいプロトタイプを指すようになります

newPrototype = Object.create(Array.prototype); を作成します。

メソッドを ["push"、"pop"、"shift"、"unshift"、"reverse"、"sort"、"splice"] とします。

メソッド.forEach(メソッド => {
  newPrototype[メソッド] = function(...args) {
    console.log(`${method} が使用されました`);
    挿入します。
    スイッチ(メソッド){
      ケース「プッシュ」:
      ケース「unshift」:
        挿入された = 引数;
        壊す;
      ケース「スプライス」:
        挿入 = args.slice(2);
        壊す;
      デフォルト:
        壊す;
    }
    挿入 && observeArr(挿入);
    Array.prototype[method].call(this, ...args); を返します。
  };
});

関数 observeArr(arr) {
  // これは条件付き制限と再帰終了条件の両方です if (!Array.isArray(arr)) {
    戻る;
  }
  // 配列全体が新しいプロトタイプを指します arr.__proto__ = newPrototype;
  // 配列内の各項目は、配列の場合は新しいプロトタイプも指します。
  arr.forEach(Arr を観察します);
}
// これをエクスポートすると、他のファイルでも使用しやすくなります export default observeArr;
// 監視する配列。新しいプロトタイプをバインドするだけです。let arr = [];
観測Arr(arr);
addItem = [1, 2, 3]とします。
arr.push(アイテムを追加);
// 実行するとaddItem.push(1)が出力されます。
アイテムを追加します。

オブジェクトと配列を監視するためのdefinePropertyの併用

これで、オブジェクトを監視するメソッドと配列を監視するメソッドができました。この 2 つを組み合わせることで、配列内のオブジェクトとオブジェクト内の配列を監視できます。

監視配列と監視オブジェクトは、後で使用するために別のファイルに書き込むことができます。

直接コード実行を容易にするために、これらはここにまとめられています。

/**
 * observeArr 部分**/
// 新しいプロトタイプを生成します。let newPrototype = Object.create(Array.prototype);

メソッドを ["push"、"pop"、"shift"、"unshift"、"reverse"、"sort"、"splice"] とします。
// 上記のメソッドを新しいプロトタイプに追加して、methods.forEach(method => {
  newPrototype[メソッド] = function(...args) {
    console.log(`${method} が使用されました`);
    挿入します。
    スイッチ(メソッド){
      ケース「プッシュ」:
      ケース「unshift」:
        挿入された = 引数;
        壊す;
      ケース「スプライス」:
        挿入 = args.slice(2);
        壊す;
      デフォルト:
        壊す;
    }
    挿入 && observeArr(挿入);
    Array.prototype[method].call(this, ...args); を返します。
  };
});

関数 observeArr(arr) {
  // 新しい! ! !オブジェクトの場合は、オブジェクトを使用する必要があります if (Object.prototype.toString.call(arr) === "[object Object]") {
    観測オブジェクト(arr)
    戻る;
  }

  Array.isArray(arr) の場合 {
    // 配列全体が新しいプロトタイプを指します arr.__proto__ = newPrototype;
    // 配列内の各項目は、配列の場合は新しいプロトタイプも指します。
    arr.forEach(Arr を観察します);
  }

  // オブジェクトでも配列でもない場合は何もしません}

/**
 * observeObj 部分**/
関数 observeObj(obj) {
  // パラメータ制限を追加します。オブジェクトのみがハイジャック可能で、これは再帰の終了条件でもあります。if (typeof obj !== "object" || obj == null) {
    戻る;
  }
  // 新しい! ! !配列は処理のために配列に渡されます if (Array.isArray(obj)) {
    観測Arr(obj);
    戻る;
  }
  // オブジェクトの場合のみ再帰を開始する for (let key in obj) {
    // obj.hasOwnProperty を直接使用すると非標準のプロンプトが表示されます if (Object.prototype.hasOwnProperty.call(obj, key)) {
      obj を observeKey します。
      // ここで、属性の属性値がハイジャックされます。オブジェクトでない場合は、observeObj(obj[key]) に影響を与えずに直接返されます。
    }
  }
  obj を返します。
}
関数 observeKey(obj, key) {
  値をobj[キー]とします。
  Object.defineProperty(obj, キー, {
    得る() {
      console.log("プロパティを読み取り", 値);
      戻り値;
    },
    set(新しい値) {
      console.log("プロパティを設定する", newValue);
      値 = 新しい値;
    }
  });
}

/**
 * デモを試す**/
データ = {a: 1、b: [1、2、{c: 2 }] }とします。
observeObj(データ);
データ.a = 2;
データ.b.push([2, 3]);

arr = [{ a: "配列内のオブジェクト" }, 3, 4]とします。
観測Arr(arr);
arr[0].a = 3;

欠陥

もちろん、メソッドを使わずに配列を変更することもできます。たとえば、length属性を使用して配列を削除したり、arr[0]=xxxを使用して配列を直接変更したりできます。

ただし、配列の変更は、「push」、「pop」、「shift」、「unshift」、「reverse」、「sort」、「splice」を使用する場合にのみ検出できます。

これも Vue の欠陥です。もちろん、新しいバージョンのプロキシではこの欠陥は解消されます。

なのでvueを使うときは上記の方法で配列を操作してみてください~~~

注: 配列のすべてのプロパティとメソッドを表示する

コンソールに dir([]) と入力すると、配列のすべてのプロパティとメソッドが表示されます。

具体的な使用方法については、mdnに直接アクセスし、サイドバーをクリックして対応する方法を確認してください。

要約する

JavaScript で配列の変更を監視する方法についての記事はこれで終わりです。JS による配列の変更の監視に関するより関連性の高いコンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JavaScript配列についてさらに詳しく知るのに役立つ記事
  • Javascript配列の重複排除のいくつかの方法の詳細な説明
  • JavaScript配列の組み込みメソッドの詳細な説明
  • よく使われるJavaScript配列メソッド
  • JavaScript 配列の詳細な概要
  • JavaScriptでよく使われる配列重複排除実戦ソースコード
  • JS オブジェクト配列の重複排除のための 3 つの方法の例と比較
  • JSは単純なフィルタリングから複数条件のフィルタリングまで配列フィルタリングを実装します
  • JavaScript 配列の Reduce() メソッドの構文と例の分析
  • JavaScript で 24 以上の配列メソッドを手動で実装する

<<:  Centos8 でローカル Web サーバーを構築するための実装手順

>>:  mysql 8.0.16 winx64 および Linux でルート ユーザーのパスワードを変更する方法

推薦する

Centos7 に mysql 8.0.13 (rpm) をインストールする詳細なチュートリアル

yum か rpm か? yum によるインストール方法は非常に便利ですが、公式サイトから MySQ...

Nginx 500 内部サーバーエラーの解決方法

今日、Nginxを使っていたら500エラーが発生しました。エラーコードを検索してみんなに共有しました...

mysqlは、現在の時刻が開始時刻と終了時刻の間にあるかどうかを判断し、開始時刻と終了時刻が空であることが許可されます。

目次要件: 進行中のアクティビティ データを照会する次のSQLクエリは、上記の4つの要件を満たし、タ...

MySQL 学習データベースバックアップの詳細な説明

目次1.DB、DBMS、SQL 2. データベースの特徴3. SQL分類4. MySQLを起動および...

vue cli3は環境ごとにパッケージ化の手順を実装します

cli3 でビルドされた vue プロジェクトは、ゼロ構成ファイルとして知られています。パッケージ化...

MySQL のインデックスの原理とクエリの最適化の詳細な説明

目次1. はじめに1. インデックスとは何ですか? 2. インデックスはなぜ必要なのでしょうか? 2...

MySQL データベースに基づくデータ制約の例と 5 つの整合性制約の紹介

非準拠データがデータベースに入るのを防ぐために、ユーザーがデータを挿入、変更、削除、その他の操作を行...

{{ }} で関数を直接使用する WeChat アプレットの例

序文WeChat アプレット開発 (ネイティブ wxml、wxcss) で、{{ }} 内で直接メソ...

スライド階段効果を実現するjQuery

この記事では、階段スライド効果を実現するためのjQueryの具体的なコードを参考までに紹介します。具...

MySQL 外部キー制約とテーブル関係の概要

目次外部キーテーブルの関係を決定する方法テーブル関係を作成する方法1対多の関係 - 従業員テーブルと...

Linux sftp コマンドの使用法

SFTPの概念sftp は、安全なファイル転送プロトコルである Secure File Transf...

MySQLデータベースがNULLを可能な限り避ける理由

MySQL の多くのテーブルには、NULL が列のデフォルト属性であるため、アプリケーションが NU...

vue-admin-template 動的ルーティング実装例

ログインを提供し、ユーザー情報データインターフェースを取得するapi/user.js内 '@...

SpringBoot を MySQL に接続してデータを取得し、バックエンド インターフェースに書き込む方法

目次1. 新しいプロジェクトを作成する2. 依存関係を追加する3. SpringコンテナにDrive...

MySQL テーブル全体の暗号化ソリューション keyring_file の詳細な説明

例示するMySql Community Edition は、5.7.11 以降、テーブルベースのデー...