オブジェクトはJavaScriptの基本的なブロックです。 オブジェクトはプロパティのコレクションであり、プロパティはキー(または名前)と値の間の関連付けです。 JavaScriptのほとんどすべてのオブジェクトはのインスタンスです Object これはプロトタイプチェーンの一番上にあります。

序章

ご存知のように、代入演算子はオブジェクトのコピーを作成せず、オブジェクトへの参照を割り当てるだけです。次のコードを見てみましょう。

let obj = {
  a: 1,
  b: 2,
};
let copy = obj;

obj.a = 5;
console.log(copy.a);
// Result 
// a = 5;

The obj 変数は、初期化された新しいオブジェクトのコンテナです。 The copy 変数は同じオブジェクトを指しており、そのオブジェクトへの参照です。 だから基本的にこれ { a: 1, b: 2, } オブジェクトは言っています:私にアクセスする方法は2つあります。 あなたは通過する必要があります obj 変数または copy あなたがまだ私に到達する方法と、これらの方法(ゲートウェイ)を介して私に行うことはすべて私に影響を与えます。

最近、不変性が広く話題になっているので、この呼び出しに耳を傾ける必要があります。 このメソッドは、あらゆる形式の不変性を削除し、元のオブジェクトがコードの別の部分で使用された場合にバグを引き起こす可能性があります。

オブジェクトをコピーする素朴な方法

オブジェクトをコピーする素朴な方法は、元のオブジェクトをループして、各プロパティを次々にコピーすることです。 このコードを見てみましょう:

function copy(mainObj) {
  let objCopy = {}; // objCopy will store a copy of the mainObj
  let key;

  for (key in mainObj) {
    objCopy[key] = mainObj[key]; // copies each property to the objCopy object
  }
  return objCopy;
}

const mainObj = {
  a: 2,
  b: 5,
  c: {
    x: 7,
    y: 4,
  },
}

console.log(copy(mainObj));

固有の問題

  1. objCopy オブジェクトには新しい Object.prototype とは異なる方法 mainObj オブジェクトのプロトタイプメソッド。これは私たちが望んでいることではありません。 元のオブジェクトの正確なコピーが必要です。
  2. プロパティ記述子はコピーされません。 値がfalseに設定された「書き込み可能な」記述子は、 objCopy 物体。
  3. 上記のコードは、の列挙可能なプロパティのみをコピーします mainObj.
  4. 元のオブジェクトのプロパティの1つがオブジェクト自体である場合、それはコピーと元のオブジェクトの間で共有され、それぞれのプロパティが同じオブジェクトを指すようになります。

浅いコピーオブジェクト

ソースの最上位プロパティが参照なしでコピーされ、値がオブジェクトであり、参照としてコピーされるソースプロパティが存在する場合、オブジェクトは浅くコピーされたと言われます。 ソース値がオブジェクトへの参照である場合、その参照値のみがターゲットオブジェクトにコピーされます。

浅いコピーは最上位のプロパティを複製しますが、ネストされたオブジェクトは元の(ソース)とコピー(ターゲット)の間で共有されます。

Object.assign()メソッドの使用

Object.assign()メソッドは、列挙可能なすべての独自のプロパティの値を1つ以上のソースオブジェクトからターゲットオブジェクトにコピーするために使用されます。

let obj = {
  a: 1,
  b: 2,
};
let objCopy = Object.assign({}, obj);
console.log(objCopy);
// Result - { a: 1, b: 2 }

さて、これはこれまでのところ仕事をします。 のコピーを作成しました obj. 不変性が存在するかどうかを見てみましょう。

let obj = {
  a: 1,
  b: 2,
};
let objCopy = Object.assign({}, obj);

console.log(objCopy); // result - { a: 1, b: 2 }
objCopy.b = 89;
console.log(objCopy); // result - { a: 1, b: 89 }
console.log(obj); // result - { a: 1, b: 2 }

上記のコードでは、プロパティの値を変更しました 'b'objCopy に反対する 89 変更されたものをログに記録するとき objCopy コンソールのオブジェクトの場合、変更はにのみ適用されます objCopy. コードの最後の行は、 obj オブジェクトはまだ無傷であり、変更されていません。 これは、ソースオブジェクトへの参照なしでソースオブジェクトのコピーが正常に作成されたことを意味します。

Object.assign()の落とし穴

そんなに早くない! コピーの作成に成功し、すべてが正常に機能しているように見えますが、浅いコピーについて説明したことを覚えていますか? この例を見てみましょう:

let obj = {
  a: 1,
  b: {
    c: 2,
  },
}
let newObj = Object.assign({}, obj);
console.log(newObj); // { a: 1, b: { c: 2} }

obj.a = 10;
console.log(obj); // { a: 10, b: { c: 2} }
console.log(newObj); // { a: 1, b: { c: 2} }

newObj.a = 20;
console.log(obj); // { a: 10, b: { c: 2} }
console.log(newObj); // { a: 20, b: { c: 2} }

newObj.b.c = 30;
console.log(obj); // { a: 10, b: { c: 30} }
console.log(newObj); // { a: 20, b: { c: 30} }

// Note: newObj.b.c = 30; Read why..

obj.bc = 30なのはなぜですか?

さて、それはの落とし穴です Object.assign(). Object.assign 浅いコピーのみを作成します。 両方 newObj.bobj.b 個々のコピーが作成されなかったため、オブジェクトへの同じ参照を共有します。代わりに、オブジェクトへの参照がコピーされました。 オブジェクトのプロパティに加えられた変更は、オブジェクトを使用するすべての参照に適用されます。 どうすればこれを修正できますか? 続きを読む…次のセクションで修正があります。

注:プロトタイプチェーンのプロパティと列挙できないプロパティはコピーできません。 ここを参照してください:

let someObj = {
  a: 2,
}

let obj = Object.create(someObj, { 
  b: {
    value: 2,  
  },
  c: {
    value: 3,
    enumerable: true,  
  },
});

let objCopy = Object.assign({}, obj);
console.log(objCopy); // { c: 3 }

  • someObj objのプロトタイプチェーン上にあるため、コピーされません。
  • property b 列挙できないプロパティです。
  • property c 列挙可能なプロパティ記述子があり、列挙可能にすることができます。 それがコピーされた理由です。

オブジェクトのディープコピー

ディープコピーは、遭遇するすべてのオブジェクトを複製します。 コピーと元のオブジェクトは何も共有しないため、元のオブジェクトのコピーになります。 これが私たちが使用して遭遇した問題の修正です Object.assign(). 探検しましょう。

JSON.parse(JSON.stringify(object));を使用する

これにより、以前に発生した問題が修正されます。 今 newObj.b 参照ではなくコピーがあります! これは、オブジェクトをディープコピーする方法です。 次に例を示します。

let obj = { 
  a: 1,
  b: { 
    c: 2,
  },
}

let newObj = JSON.parse(JSON.stringify(obj));

obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } (New Object Intact!)

不変:✓

落とし穴

残念ながら、このメソッドを使用してユーザー定義のオブジェクトメソッドをコピーすることはできません。 下記参照。

オブジェクトメソッドのコピー

メソッドは、関数であるオブジェクトのプロパティです。 これまでの例では、メソッドを使用してオブジェクトをコピーしていません。 今それを試して、コピーを作成するために学んだ方法を使用してみましょう。

let obj = {
  name: 'scotch.io',
  exec: function exec() {
    return true;
  },
}

let method1 = Object.assign({}, obj);
let method2 = JSON.parse(JSON.stringify(obj));

console.log(method1); //Object.assign({}, obj)
/* result
{
  exec: function exec() {
    return true;
  },
  name: "scotch.io"
}
*/

console.log(method2); // JSON.parse(JSON.stringify(obj))
/* result
{
  name: "scotch.io"
}
*/

結果は次のことを示しています Object.assign() メソッドをコピーするために使用できます JSON.parse(JSON.stringify(obj)) 使用できません。

円形オブジェクトのコピー

円形オブジェクトは、それ自体を参照するプロパティを持つオブジェクトです。 これまでに学んだオブジェクトをコピーする方法を使用して、円形のオブジェクトのコピーを作成し、それが機能するかどうかを確認してみましょう。

JSON.parse(JSON.stringify(object))を使用する

やってみよう JSON.parse(JSON.stringify(object)):

// circular object
let obj = { 
  a: 'a',
  b: { 
    c: 'c',
    d: 'd',
  },
}

obj.c = obj.b;
obj.e = obj.a;
obj.b.c = obj.c;
obj.b.d = obj.b;
obj.b.e = obj.b.c;

let newObj = JSON.parse(JSON.stringify(obj));

console.log(newObj); 

結果は次のとおりです。

JSON.parse(JSON.stringify(obj)) 明らかに円形のオブジェクトでは機能しません。

Object.assign()の使用

やってみよう Object.assign():

// circular object
let obj = { 
  a: 'a',
  b: { 
    c: 'c',
    d: 'd',
  },
}

obj.c = obj.b;
obj.e = obj.a;
obj.b.c = obj.c;
obj.b.d = obj.b;
obj.b.e = obj.b.c;

let newObj2 = Object.assign({}, obj);

console.log(newObj2); 

結果は次のとおりです。

Object.assign() 円形オブジェクトの浅いコピーには問題なく機能しますが、深いコピーには機能しません。 お気軽に circular object tree ブラウザコンソールで。 きっとたくさんの面白い仕事が行われていることでしょう。

スプレッド要素の使用(…)

ES6には、配列を破棄する割り当て用の残りの要素と、実装されている配列リテラル用の拡散要素がすでにあります。 ここで、配列へのスプレッド要素の実装を見てください。

const array = [
  "a",
  "c",
  "d", {
    four: 4
  },
];
const newArray = [...array];
console.log(newArray);
// Result 
// ["a", "c", "d", { four: 4 }]

オブジェクトリテラルのspreadプロパティは、現在、ECMAScriptのステージ3の提案です。 オブジェクト初期化子のスプレッドプロパティは、独自の列挙可能なプロパティをソースオブジェクトからターゲットオブジェクトにコピーします。 以下の例は、提案が受け入れられた後、オブジェクトをコピーするのがいかに簡単であるかを示しています。

let obj = {
  one: 1,
  two: 2,
}

let newObj = { ...obj };

// { one: 1, two: 2 }

注:これは浅いコピーにのみ有効です

結論

JavaScriptでオブジェクトをコピーすることは、特にJavaScriptを初めて使用し、言語の使い方がわからない場合は、非常に困難な場合があります。 この記事が、オブジェクトのコピーで発生する可能性のある将来の落とし穴を理解し、回避するのに役立つことを願っています。 より良い結果を達成するライブラリまたはコードがある場合は、コミュニティと共有することを歓迎します。 ハッピーコーディング!