blog

[中級フロントエンド開発者は知っておくべき】ディープコピー・シャローコピーの話

jsのディープコピー、シャローコピーの問題はプロジェクト開発プロセスで頻繁に発生します。有資格のフロントエンド開発者にとって、それらを深く理解することが必要であり、合理的に問題に対処するためにディープ...

Feb 15, 2020 · 6 min. read
シェア

はじめに

jsのディープコピー、プロジェクトの開発プロセスにおける浅いコピーの問題は頻繁にあります。有資格のフロントエンド開発者にとって、それらを深く理解することが必要であり、合理的に問題に対処するために深いコピー浅いコピーを使用することができます。

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

以下は私の理解です:

ディープコピー:オブジェクトaおよびbがディープコピーである場合、それらはもはや同じメモリアドレスを参照しません。

浅いコピー:オブジェクトaはオブジェクトbへのポインタをコピーするだけで、オブジェクトそのものをコピーするわけではありません。新旧のオブジェクトは同じメモリを共有します。

さて、このような小さなアイデアを頭に思い浮かべた後は、もっと深い記憶の話をしましょう。

javascript

ヒープとスタック どちらも一時的なデータを保存する場所です。

バケツと同じで、先に入ったものが先に出てきて、その下にあったものは他のものが出てきてからでないと出てきません。

ヒープとは、プログラムのコンパイル時ではなく、プログラムの実行時に要求される一定の大きさのメモリ空間のことです。つまり、メモリは動的に割り当てられ、そこへのアクセスは一般的なメモリへのアクセスと変わりません。ヒープを使えば、変数を追加したり削除したりするのも思いのままです。

スタック領域は、関数のパラメータやローカル変数の値を格納するために、コンパイラ によって自動的に割り当てと解放が行われます。 ヒープ領域は一般にプログラマが割り当て、解放します。 プログラマが解放しない場合、プログラム終了時にOSが回収することがあります。 ヒープ:ヒープソートなど、ヒープをツリーとして見ることができます。スタック:先入れ先出しのデータ構造。

データ型アクセス&コピー

** 基本データ型:スタック上に定義された変数で、その変数の値に直接アクセスするもの。** 例:var a = 1;

a1

** 基本的なデータ型のコピー:コピーの際、スタック上に新しい値が作成され、その値が新しい変数に割り当てられた場所にコピーされます。**

例:var b=a;

a1
b1

** 参照データ型:スタック上で定義されるが、その値はヒープ上のアドレスを指します。** 例

let obj=Object.create(null);
obj={a:1};

** 参照データ型のコピー:コピーはスタックに格納されたポインターをスタックにコピーし、新しい変数用にスペースを確保します。このポインタのコピーと元のポインタは、ヒープに格納されている同じオブジェクトを指します。コピー操作が終わると、両方の変数は実際には同じオブジェクトを指します。したがって、使用中に一方の変数の値を変更すると、もう一方の変数にも影響します。** 例

let obj=Object.create(null);
obj={a:1};
let obj2=obj;

さて、データ構造を理解した後は、ディープコピーとシャローコピーの実装方法について説明しましょう。

浅いコピーと深いコピーの実装方法

間違いなく、浅いコピーの実装は、ほとんどの人が考えることができるもので、プロジェクトの開発の過程で、それは時々、データの浅いコピーのためであり、一連のデータの問題を引き起こします。

ソースデータ

let data={
 name:'ddd',
 type:'es',
 impl:[
 {
 e:1,
 a:2
 },
 {
 e:2,
 a:3
 }
 ]
}

シャローコピーの実装方法

  • 最初のケース:データソースのデータを別のオブジェクトに直接代入します。 次のコードの結果を推測してください。
let obj=data; // 
obj.name='aa' //objの名前が変わると、データの名前も変わる。
console.log(obj1,data); 
  • つ目のケース:データ・ソース・データを別のオブジェクトの子にします。次のコードの結果を推測してください。

let obj1={data}; //obj1をオブジェクトとして定義し、dataをobj1の子にする。
obj1.name='bb' //obj1にname属性を追加しても、データのname値は変わらない。
obj1.data.impl[0].e=4; //ただし、objのdataプロパティの値を変更すると、データ・ソースのデータが変更される
obj1.data.name='pp';
console.log(obj1,data);
  • 第3のケース:es6拡張記号{.....XXX}を使用します。次のコードの結果を推測してください。
let obj2={...data}; 
obj2.name='cc' 
obj2.impl[0].e=3
console.log(obj2,data);

3つ目のケースは特殊ですが、JavaScriptではよくあることです。あるときはディープコピー、あるときはシャローコピーで、最初の属性値がオブジェクトでない場合のみディープコピー、それ以外はシャローコピーです。 ディープコピーとシャローコピーの特殊な構文をまとめると、次のようになります:

1、トラバーサルループのコピーのfor

2. es6 Object.assign({ },obj)

3, {... obj}

4. 連結

5.スライス

さて、上記3つのシナリオの結果は、順に以下の通り:

1例目: 2例目: 3例目

そして、どうすれば真のコピーを達成できるのでしょうか?つまり、本当に古くて死んでいて、まったく関係がない?

ディープコピーの実装方法

  • JSONオブジェクトに対するparseとstringfyの使用。

JSON.stringfyはjsオブジェクトの値をJSON文字列に変換し、JSON.parseはJSON文字列をjsオブジェクトに変換します。JSON.parseは、JSON文字列をjsオブジェクトに変換します。まず文字列に変換し、次にオブジェクトに変換することで、結果のオブジェクトと元のオブジェクトは同じメモリを共有せず、ディープコピーの処理を実現します。

しかし問題があります。undefined、function、symbolは変換で無視されます。

追記:フィルタリングにはJSON.stringfy(parameter1, parameter2, parameter3)を使います。

 var foo = {
 bar: "1",
 baz: "3",
 o: {
 name: 'xiaolo',
 age: 21,
 info: {
 sex: 'nan',
 getSex: function() {
 return 'nan';
 }
 }
 }
 }
 var f = JSON.stringify(foo, cencer);
 var c = JSON.parse(f);
 console.log(c);

以下:

  • 再帰関数を使って

このアイデアは、オブジェクトをループして、オブジェクトのプロパティの値がまだオブジェクトであるかどうかを判断し、もしオブジェクトであれば再帰的に判断するというものです。

function cloneData(obj){
 let target;
 if(typeof obj==='object' && obj!==null){
 if(Array.isArray(obj)){
 target=[];
 }else{
 target={};
 }
 }else{
 return target=obj;
 }
 for(let key in obj){
 if(obj.hasOwnProperty(key)){
 if(typeof obj[key] ==='object'){
 target[key]=cloneData(obj[key]);
 }else{
 target[key]=obj[key];
 }
 }
 }
 return target;
}

追記:上記のcloneDataはオブジェクト・リングに対してコピーすることができません。そこで、オブジェクトリングの問題を考慮してcloneData関数を修正する必要があります。

function checktype(obj) {
 //オブジェクトの型をチェックする
 return Object.prototype.toString.call(obj).slice(8, -1)
}
function depCopy(target, hash = new WeakMap()) {
 //hash オブジェクトのディープコピーにおけるリング参照を回避するチェッカーとして機能する。
 let type = checktype(target)
 let result = null
 if (type == 'Object') {
 result = {}
 } else if (type == 'Array') {
 result = []
 } else {
 return target
 }
 if (hash.has(target)) {
 //同じオブジェクトがコピーされていないかチェックし、コピーされていればコピーされたオブジェクトをハッシュに格納する。
 return hash.get(target)
 }
 hash.set(target, result) //バックアップはハッシュに格納され、結果は現在空のオブジェクト、配列になっている。後で属性を追加するが、ここに格納される値はオブジェクトのスタックである。
 for (let i in target) {
 if (
 checktype(target[i]) == 'Object' ||
 checktype(target[i]) == 'Array'
 ) {
 result[i] = depCopy(target[i], hash) //属性値は再帰的ディープコピー用のオブジェクトである。
 } else {
 result[i] = target[i] //その他のダイレクトコピーの種類
 }
 }
 return result
}
  • loadshを実装するサードパーティライブラリ_.cloneDeep

既製のAPIを使用してディープコピーを実装するためのサードパーティライブラリを紹介します。

var obj={
 a:{
 b:2,
 c:3
 }
 }
 var obj1=_.cloneDeep(obj);
 obj1.a.c=6;
Read next

Nodeの概要

09rdはV8をベースに軽量なWebサーバとライブラリ一式を構築しています。 1.なぜJSなのか 高性能なウェブサーバを構築する鍵は、イベントドリブン、非同期IOであり、これはJSによって自然にサポートされます。 2.なぜNodeなのか NodeはWebアプリケーションを構築するための基本的なフレームワークとなっており、アプリケーションを構築するための基盤として使用することができます。各Nodeは、シングルスレッドの単一プロセスとして、大規模な分散アプリケーションを構築するための ...

Feb 15, 2020 · 2 min read