JavaScript のディープコピーの落とし穴

JavaScript のディープコピーの落とし穴

序文

以前、ある会社の面接に行ったとき、面接官から「オブジェクトを深くコピーするにはどうすればよいですか?」という質問を受けました。その時、私は密かに嬉しく思いました。こんな単純な疑問を考える必要があるのでしょうか?そこで私はこう言いました。「よく使われる方法は 2 つあります。1 つ目は JSON.parse(JSON.stringify(obj)) を使用する方法で、2 つ目は for...in と再帰を使用する方法です。」これを聞いた面接官はうなずき、満足そうでした。
当時はこの問題についてあまり気にしていませんでしたが、しばらく前にもう一度考えてみると、上記の両方の方法にバグがあることがわかりました。

質問する

では、上で述べたバグとは何でしょうか?

特殊オブジェクトのコピー

まず、共通の型を考慮せずに、次のメンバーを持つオブジェクトを想像してみましょう。

定数オブジェクト = {
    編曲: [111, 222],
    obj: {キー: 'オブジェクト'},
    a: () => {console.log('function')},
    日付: 新しい Date(),
    登録: /regular/ig
}

次に、上記の2つの方法をそれぞれ使用してコピーします。

JSON メソッド

JSON.parse(JSON.stringify(obj))

出力:

obj 内の通常のオブジェクトと配列の両方をコピーできることがわかりますが、日付オブジェクトは文字列になり、関数は直接消え、正規表現は空のオブジェクトになります。
再帰を使ったfor...inメソッドを見てみましょう

再帰

関数isObj(obj) {
    戻り値 (typeof obj === 'オブジェクト' || typeof obj === '関数') && obj !== null
}
関数 deepCopy(obj) {
    tempObj = Array.isArray(obj) ? [] : {} とします。
    for(let key in obj) {
        tempObj[key] = isObj(obj[key]) ? deepCopy(obj[key]) : obj[key]
    }
    tempObjを返す
}

結果:

結論は

上記のテストから、これら 2 つのメソッドは function、date、reg タイプのオブジェクトをコピーできないことがわかります。

  • 指輪

リングとは何ですか?

ループはオブジェクト間の循環参照であり、閉じたループになります。たとえば、次のオブジェクトがあります。

var a = {}

aa = ア

上記の2つの方法でコピーすると、エラーが直接報告されます。

解決

  • 指輪

コピーされたオブジェクトを格納するために WeakMap 構造体を使用することができます。オブジェクトをコピーするたびに、WeakMap を照会して、オブジェクトがコピーされたかどうかを確認します。コピーされた場合は、オブジェクトを取り出して返します。deepCopy 関数を次のように変換します。

関数 deepCopy(obj, hash = new WeakMap()) {
    hash.has(obj) の場合、hash.get(obj) を返します。
    cloneObj = Array.isArray(obj) ? [] : {} とします。
    ハッシュを設定します(obj、クローンObj)
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
    }
    cloneObjを返す
}

リング結果をコピー:

特殊オブジェクトのコピー

この問題の解決法は、特別に扱う必要があるオブジェクトの種類が多すぎるため、かなり複雑です。そこで、MDN の構造化コピーを参照し、それをリングの解決法と組み合わせました。

// 日付と登録型のみを解決し、他の型は自分で追加できます。 function deepCopy(obj, hash = new WeakMap()) {
    cloneObj を作ろう
    コンストラクタ = obj.constructor
    switch(コンストラクタ){
        ケース正規表現:
            cloneObj = 新しいコンストラクタ(obj)
            壊す
        事件日:
            cloneObj = 新しいコンストラクタ(obj.getTime())
            壊す
        デフォルト:
            hash.has(obj) の場合、hash.get(obj) を返します。
            cloneObj = 新しいコンストラクタ()
            ハッシュを設定します(obj、クローンObj)
    }
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
    }
    cloneObjを返す
}

コピー結果:

完全版については、lodash deep copyをご覧ください。

  • 関数のコピー

ただし、MDN の構造化コピーでは、関数のコピーの問題は依然として解決されません。

これまでは関数をコピーするために eval メソッドを使うことしか考えていませんでしたが、この方法はアロー関数にしか機能しません。fun(){} の形式だと失敗します。

関数をコピーして関数タイプを追加する

結果をコピー

エラーの種類

追記

JavaScript のディープ コピーには、上記で説明した問題以外にも多くの問題があります。もう 1 つの問題は、プロトタイプ チェーン上のプロパティをどのようにコピーするかということです。列挙不可能なプロパティをコピーする方法、エラー オブジェクトをコピーする方法などについては、ここでは詳しく説明しません。

ただし、日常生活では依然として JSON メソッドを使用することをお勧めします。このメソッドはほとんどのビジネス ニーズをカバーしているため、単純なことを複雑にする必要はありません。ただし、面接中に細かいことを気にする面接官に遭遇した場合、この質問に対する答えは間違いなく面接官の印象を良くするでしょう。

これで、JavaScript ディープ コピーの落とし穴に関するこの記事は終了です。JavaScript ディープ コピーに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JavaScriptの浅いコピーと深いコピーについての簡単な説明
  • js における浅いコピーと深いコピーの詳細な説明
  • JS変数ストレージのディープコピーとシャローコピーの詳しい説明
  • JS オブジェクトのコピー (ディープ コピーとシャロー コピー)
  • JSのディープコピーとシャローコピーの詳しい説明
  • jsのディープコピーを理解しましょう

<<:  alpineをベースにdockerfileで作成したクローラーScrapyイメージの実装

>>:  mysql mycat ミドルウェアのインストールと使用

推薦する

Mysql ALTER TABLE はフィールドを追加するときにテーブルをロックしますか?

目次MySQL 5.6以前MySQL 5.6以降要約する知らせMySQL 5.6以前更新手順元のテー...

Windows での MySQL5 グリーン バージョンのインストールの概要 (推奨)

1 MySQLをダウンロードするダウンロードアドレス: http://downloads.mysq...

NginxはGzipアルゴリズムを使用してメッセージを圧縮します

HTTP圧縮とは場合によっては、比較的大きなメッセージ データがクライアントとサーバー間で送信され、...

nginxとlvsのメリットとデメリット、そして適切な使用環境

まず最初に、ロード バランシングとは何かについて説明します。ロード バランシングとは、リクエストの内...

Linux ディスクのシーケンシャル書き込みとランダム書き込みの方法

1. はじめに● ランダム書き込みではヘッドがトラックを頻繁に変更するため、効率が大幅に低下します。...

フロントエンドのパフォーマンス最適化を学ぶ準備として、HTMLページのレンダリングプロセスを理解する

現在、フロントエンドのパフォーマンス最適化について学んでいます。適切な解決策を見つけ、パフォーマンス...

Reactでpropsを使用する方法と制限する方法

コンポーネントの props (props はオブジェクトです)機能: コンポーネントに渡されたデー...

Navicat for SQLite で中国語データを CSV にインポートする方法

この記事では、参考までに、csv中国語データをNavicat for SQLiteにインポートする具...

CSS で div にスクロールを追加し、スクロール バーを非表示にする

CSS は div にスクロールを追加し、スクロール バーを非表示にします。具体的なコードは次のとお...

IE8 互換性について: X-UA-compatible 属性の説明

問題の説明:コードをコピーコードは次のとおりです。 <meta http-equiv=&quo...

Vue3は独自のページングコンポーネントをカプセル化します

この記事の例では、vue3 が独自のページングコンポーネントをカプセル化する具体的なコードを参考まで...

MySQL 5.7.23 winx64 のインストールと設定方法のグラフィックチュートリアル (win10 の場合)

この記事はMySQL 5.7.23 winx64のインストールチュートリアルを記録します。具体的な内...

一般的な MySQL ストレージ エンジンとパラメータ設定およびチューニングの紹介

MyISAM、MySQLでよく使われるストレージエンジン特性: 1. 同時実行性とロックレベル2. ...

プライベートDockerリポジトリであるHarborをインストールするための詳細な手順

Harborのインストールは非常に簡単ですが、Dockerログインで行き詰まってしまいました。このブ...

Js でオブジェクトのディープ オブジェクトを安全に取得するメソッドの例

目次序文文章パラメータ例Lodash 実装:トーキー機能: castPath関数: stringTo...