JS変数ストレージのディープコピーとシャローコピーの詳しい説明

JS変数ストレージのディープコピーとシャローコピーの詳しい説明

可変タイプとストレージスペース

スタックメモリとヒープメモリ

基本的なデータ型

文字列、数値、null、未定義、ブール値、シンボル(ES6 の新機能)変数の値はスタック メモリに保存され、変数の値に直接アクセスして変更できます。基本データ型にはコピーがないため、たとえば、1 の値を変更することはできません。

参照タイプ

オブジェクト 関数 RegExp 数学 日付 値はオブジェクトであり、ヒープ メモリに格納されます。スタック メモリ内の変数には、ヒープ メモリ内の対応するアドレスへのポインタが格納されます。
参照型にアクセスするときは、まずスタックからオブジェクトのアドレス ポインターを取得し、次にヒープ メモリから必要なデータを取得する必要があります。

グラフィックストレージスペース

let a1 = 0; // スタックメモリ let a2 = "this is string" // スタックメモリ let a3 = null; // スタックメモリ let b = { x: 10 }; // 変数 b はスタックに存在し、{ x: 10 } はヒープ内のオブジェクトとして存在します let c = [1, 2, 3]; // 変数 c はスタックに存在し、[1, 2, 3] はヒープ内のオブジェクトとして存在します

参照タイプの割り当て

a = {x: 10, y: 20}とします。
b = a とします。
5 倍
コンソールログ(ax); // 5 

ディープコピーとシャローコピー

ディープコピー

オブジェクトをメモリから完全にコピーし、ヒープメモリ内に新しい領域を開いて新しいオブジェクトを格納し、新しいオブジェクトを変更しても元のオブジェクトには影響しません。

浅いコピー

浅いコピーはオブジェクトのビット単位のコピーであり、元のオブジェクトのプロパティ値の正確なコピーを持つ新しいオブジェクトを作成します。属性がプリミティブ型の場合はプリミティブ型の値がコピーされ、属性がメモリ アドレス (参照型) の場合はメモリ アドレスがコピーされます。

オブジェクトの割り当て

オブジェクトを新しい変数に割り当てる場合、実際に割り当てられるのはヒープ内のデータではなく、スタック内のオブジェクトのアドレスです。つまり、2 つのオブジェクトは同じストレージ スペースを指します。どちらのオブジェクトが変更されても、実際に変更されるのはストレージ スペースの内容です。したがって、2 つのオブジェクトはリンクされています。

3つの比較

浅いコピーの5つの一般的な方法

オブジェクト.assign()

Object.assign() メソッドは、ソース オブジェクト自身の列挙可能なプロパティを任意の数だけターゲット オブジェクトにコピーし、ターゲット オブジェクトを返すことができます。しかしObject.assign()は浅いコピーを実行する

Object.assignはソースオブジェクト(sources)のすべてのプロパティを左から右に走査し、=を使用してターゲットオブジェクト(target)に割り当てます。

var obj = { a: {a: "kobe", b: 39},b:1 };
        var initalObj = Object.assign({}, obj);
        initalObj.aa = "ウェイド";
        初期オブジェクトb = 2;
        console.log(obj.aa); //ウェイド
        コンソールログ(obj.b); //1

スプレッド演算子

obj = {a:1,b:{c:1}}とします。
obj2 = {...obj} とします。
2 番目の引数は 0 です。
console.log(obj); //{a:2,b:{c:1}}
コンソールログ(obj2); //{a:1,b:{c:1}}

obj.bc = 2;
console.log(obj); //{a:2,b:{c:2}}
コンソールログ(obj2); //{a:1,b:{c:2}}

配列.プロトタイプ.スライス

slice() メソッドは、begin と end (end を除く) によって決定される元の配列の浅いコピーである新しい配列オブジェクトを返します。元の配列の基本型は変更されませんが、参照型は変更されます。

arr = [1, 3, {
    ユーザー名: 'kobe'
    }];
arr3 = arr.slice() とします。
0 は 0 です。
arr3[2].ユーザー名 = 'ウェイド'
コンソールにログ出力します。

配列.プロトタイプ.連結()

arr = [1, 3, {
    ユーザー名: 'kobe'
    }];
arr2=arr.concat() とします。   
0 は 0 です。
arr2[2].ユーザー名 = 'ウェイド';
コンソールにログ出力します。

手書きの浅いコピー

関数 shallowCopy(src) {
    var dst = {};
    (var prop in src) の場合 {
        src.hasOwnProperty(prop) の場合 {
            dst[prop] = src[prop];
        }
    }
    dst を返します。
}

ディープコピーの一般的な方法

jsON.parse(jsON.stringify())

JSON.stringifyを介してディープコピーを実装する際には、いくつか注意すべき点があります。

コピーされたオブジェクトの値に関数、未定義、またはシンボルが含まれている場合、JSON.stringify() によってシリアル化された JSON 文字列ではキーと値のペアが消えます。

列挙不可能なプロパティをコピーできません。オブジェクトのプロトタイプ チェーンをコピーできません。

日付参照型をコピーすると文字列になる

RegExp参照型をコピーすると空のオブジェクトが生成されます

オブジェクトにNaN、Infinity、-Infinityが含まれている場合、シリアル化された結果はnullになります。

ループ内でオブジェクトをコピーできません (例: obj[key] = obj)

arr = [1, 3, {
    ユーザー名: 'kobe'
}];
arr4 = JSON.parse(JSON.stringify(arr)) とします。
arr4[2].ユーザー名 = 'ダンカン'; 
コンソールログ(arr, arr4)

手書きの乞食版ディープコピー

まず、この deepClone 関数は列挙できないプロパティとシンボル型をコピーできません。

ここでは、ループ反復はオブジェクト参照型の値に対してのみ実行され、配列、日付、正規表現、エラー、関数参照型は正しくコピーできません。

オブジェクトがループしている、つまり循環参照になっている(例:obj1.a = obj)

関数クローン(ターゲット) {
    if (typeof target === 'object') {
        cloneTarget を Array.isArray(target) とします。[] : {};
        for (const キー in ターゲット) {
            cloneTarget[キー] = clone(target[キー]);
        }
        cloneTarget を返します。
    } それ以外 {
        ターゲットを返します。
    }
};

皇帝版ディープコピー

この例は ConardLi の github からのもので、ソース アドレスは https://github.com/ConardLi/ です。

const mapTag = "[オブジェクト Map]";
    const setTag = "[オブジェクト Set]";
    const arrayTag = "[オブジェクト配列]";
    const objectTag = "[オブジェクト オブジェクト]";
    const argsTag = "[オブジェクト引数]";

    const boolTag = "[オブジェクト ブール値]";
    const dateTag = "[オブジェクト Date]";
    const numberTag = "[オブジェクト番号]";
    const stringTag = "[オブジェクト文字列]";
    const symbolTag = "[オブジェクト シンボル]";
    const errorTag = "[オブジェクト Error]";
    const regexpTag = "[オブジェクト RegExp]";
    const funcTag = "[オブジェクト関数]";

    const deepTag = [mapTag、setTag、arrayTag、objectTag、argsTag];

    関数 forEach(配列, iteratee) {
      インデックスを -1 とします。
      定数長さ = 配列.長さ;
      (++インデックス < 長さ) の間 {
        iteratee(配列[インデックス], インデックス);
      }
      配列を返します。
    }

    関数isObject(ターゲット) {
      const type = typeof ターゲット;
      戻りターゲット !== null && (type === "オブジェクト" || type === "関数");
    }

    関数 getType(ターゲット) {
      Object.prototype.toString.call(target) を返します。
    }

    関数 getInit(ターゲット) {
      const Ctor = target.constructor;
      新しい Ctor() を返します。
    }

    関数 cloneSymbol(ターゲット) {
      Object(Symbol.prototype.valueOf.call(target)) を返します。
    }

    関数 cloneReg(ターゲット) {
      const reFlags = /\w*$/;
      const 結果 = 新しい targe.constructor(targe.source、reFlags.exec(targe));
      結果.lastIndex = ターゲット.lastIndex;
      結果を返します。
    }

    関数 cloneFunction(func) {
      定数 bodyReg = /(?<={)(.|\n)+(?=})/m;
      定数paramReg = /(?<=\().+(?=\)\s+{)/;
      定数 funcString = func.toString();
      if (関数プロトタイプ) {
        定数パラメータ = paramReg.exec(funcString);
        bodyReg は、次のコードで定義されます。
        if (本文) {
          if (パラメータ) {
            param[0]を分割します。
            新しい関数(...paramArr、body[0])を返します。
          } それ以外 {
            新しい関数(body[0])を返します。
          }
        } それ以外 {
          null を返します。
        }
      } それ以外 {
        eval(funcString) を返します。
      }
    }

    関数 cloneOtherType(ターゲット, タイプ) {
      const Ctor = target.constructor;
      スイッチ(タイプ){
        ケース boolタグ:
        ケース番号タグ:
        ケース文字列タグ:
        ケースエラータグ:
        ケース日付タグ:
          新しい Ctor(target) を返します。
        case regexpタグ:
          cloneReg(ターゲット)を返します。
        ケースシンボルタグ:
          cloneSymbol(target) を返します。
        ケース関数タグ:
          cloneFunction(ターゲット) を返します。
        デフォルト:
          null を返します。
      }
    }

    関数クローン(ターゲット、マップ = 新しいWeakMap()) {
      // 元の型を複製する if (!isObject(target)) {
        ターゲットを返します。
      }

      // 初期化 const type = getType(target);
      cloneTarget を作成します。
      deepTag.includes(type) の場合
        cloneTarget = getInit(ターゲット、タイプ);
      } それ以外 {
        cloneOtherType(ターゲット、タイプ) を返します。
      }

      // 循環参照を防ぐ if (map.get(target)) {
        map.get(target) を返します。
      }
      マップを設定します(ターゲット、クローンターゲット);

      // セットを複製する
      if (type === setTag) {
        ターゲット.forEach(値 => {
          cloneTarget.add(clone(値、マップ));
        });
        cloneTarget を返します。
      }

      // マップを複製する
      if (type === mapTag) {
        target.forEach((値, キー) => {
          cloneTarget.set(キー、clone(値、マップ));
        });
        cloneTarget を返します。
      }

      // オブジェクトと配列を複製します。const keys = type === arrayTag ? undefined : Object.keys(target);
      forEach(キー || ターゲット、(値、キー) => {
        if (キー) {
          キー = 値;
        }
        cloneTarget[key] = clone(target[key], map);
      });

      cloneTarget を返します。
    }

    定数マップ = 新しい Map();
    map.set("キー", "値");
    map.set("ConardLi", "code Secret Garden");

    set は新しい Set() です。
    set.add("ConardLi");
    set.add("コード秘密の庭");

    定数ターゲット = {
      フィールド1: 1,
      フィールド2: 未定義、
      フィールド3: {
        子供: 「子供」
      },
      フィールド4: [2, 4, 8],
      空: null、
      地図、
      セット、
      bool: 新しいブール値(true)、
      num: 新しい数値(2)、
      str: 新しい文字列(2)、
      シンボル: オブジェクト(シンボル(1))、
      日付: 新しい Date(),
      reg: /\d+/,
      エラー: 新しい Error()、
      関数1: () => {
        console.log("コード秘密の庭");
      },
      関数2: 関数(a, b) {
        a + b を返します。
      }
    };

    const 結果 = clone(ターゲット);

    console.log(ターゲット);
    console.log(結果);

以上がJS変数ストレージのディープコピーとシャローコピーの詳しい説明です。JS変数ストレージのディープコピーとシャローコピーの詳細については、123WORDPRESS.COMの他の関連記事に注目してください!

以下もご興味があるかもしれません:
  • JavaScript のディープコピーとシャローコピーの詳しい説明
  • JavaScriptの浅いコピーと深いコピーについての簡単な説明
  • js における浅いコピーと深いコピーの詳細な説明
  • JS オブジェクトのコピー (ディープ コピーとシャロー コピー)
  • JavaScriptのディープコピーとシャローコピーに関するよくある話

<<:  MySQL コマンドラインでよく使われる 18 個のコマンド

>>:  Linux ユーザー状態とカーネル状態間の通信方法の詳細な説明

推薦する

ノードの対応するバージョンに関する簡単な説明 node-sass sass-loader

目次ノードのバージョンが一致しない、ノードをアップグレードまたはダウングレードするnvm を使用して...

el-table ヘッダーでテキストを折り返す 3 つの方法の詳細な説明

目次問題の説明レンダリング3種類のコード要約する問題の説明通常、表のヘッダーは折り返されませんが、ビ...

Dockerデータ管理とネットワーク通信の使用

Docker をインストールし、Docker コアとインストールを通じて簡単な操作を実行できます。 ...

Dockerのデフォルトネットワークセグメントを変更する実装方法の分析

背景同社のサーバーはすべて Alibaba Cloud ECS ホストを購入しています。デフォルトの...

SSL を実装するために nginx を設定する方法の例

環境説明サーバーシステム: Ubuntu 18.04 64ビットnginx: 1.14この記事では主...

MySQLデータを復元する2つの方法

1. はじめに少し前、開発者がテスト環境や本番環境で誤った操作をし、データベースを誤って削除/更新し...

MySQL でのストアド プロシージャと関数の作成の詳細な説明

目次1. ストアドプロシージャ1.1. 基本構文1.2 実行権限を指定してストアドプロシージャを作成...

CSS3で線形グラデーションを実装するためのコードの詳細な説明

序文デモでは古いバージョンのブラウザのグラデーションが実装されています[IE9-]。 IE9 より前...

MySQLは効率的なインデックス例分析を確立する

この記事では、例を使用して、MySQL で効率的なインデックスを作成する方法について説明します。ご参...

テキストの円形スクロールアニメーションを実装するミニプログラム

この記事では、参考までに、テキストループスクロールを実現するアプレットの具体的なコードを例を挙げて紹...

CentOS 7 で MySQL 接続数が 214 に制限される問題の解決方法

問題を見つける最近、プロジェクトで問題が発生しました。接続が多すぎるため、「接続が多すぎます」という...

Vue px to rem 構成の詳細な説明

目次方法1 1. 構成とインストールの手順:方法2方法3要約する方法1 1. 構成とインストールの手...

CentOS 7.6 Telnetサービス構築プロセス(Opensshアップグレードバトル第一弾のバックアップトランスポートライン構築)

不明な点があるときはいつでも、Blog Park にアクセスして、いつでも答えやインスピレーションを...

Vue でのスロット配置と使用状況分析

このチュートリアルの動作環境: Windows 7 システム、vue 2.9.6 バージョン、DEL...

タグが新しいページを開くかどうかという問題。主要ウェブサイトの開設状況をまとめました

a タグが新しいページを開くかどうか: (1)百度百科事典:ヘッダーが異なる場合は新しいページが開き...