2019-11-29に投稿

JavaScript:オブジェクトが中に入った配列をコピーする

はじめに

こんなの、絶対みんな直面していると思うんだけど、調べても出てこないのは、ググり方が悪いのか、需要がないのか、みんな余裕でやってしまうのか、なんなんだ?

想定読者

下記の挙動を理解している人

const array = [1,2,3];
const array2 = array;
console.log(array); // → [1,2,3]
array2[1] = 4;
console.log(array); // → [1,4,3]

配列をディープコピーしたい

配列をコピーして別物として扱いたいとき、 = で代入してはいけないのは流石に分かるのだけど、殊更JavaScriptになると「あれ、どうやるんだっけ?」と毎回調べるハメになる。
しかも、やれsliceを使え、concatを使えだの、やれArray.fromを使えだの、様々な方法があって、かつ、そいつら、ディープコピーのメソッドじゃないよね? cloneとかないの?1 という状況なのですが、文句を言っても仕方がないので、愚直にやるわけです。

配列の中身がオブジェクトだとうまくいかない

で、こんなコードを書いたわけですが、

const array = [{a:1},{a:2}];
const array2 = array.slice();
console.log(JSON.parse(JSON.stringify(array)));
// Array(2)
// 0: {a: 1}
// 1: {a: 2}
array2[1].a = 3;
console.log(JSON.parse(JSON.stringify(array)));
// ???
// Array(2)
// 0: {a: 1}
// 1: {a: 3}

目を疑った。あれ、実はsliceはダメなのかな? とか思ってconcatもArray.fromも試したけどダメ。
1時間くらい悩んで、「ああ、配列はディープコピーされたけど、中のオブジェクトが参照のままなのか」と気がついた。

const array = [{a:1},{a:2}];
const array2 = array.slice();
console.log(JSON.parse(JSON.stringify(array)));
// Array(2)
// 0: {a: 1}
// 1: {a: 2}
array2.push({a:3});
console.log(JSON.parse(JSON.stringify(array)));
// 配列はディープコピーされている
// Array(2)
// 0: {a: 1}
// 1: {a: 2}
console.log(JSON.parse(JSON.stringify(array2)));
// Array(3)
// 0: {a: 1}
// 1: {a: 2}
// 2: {a: 3}

解決策

え、これどーすんの? と少し悩んで、下記の様にした。

const array = [{a:1},{a:2}];
const array2 = array.map((obj) => Object.assign({},obj));
console.log(JSON.parse(JSON.stringify(array)));
// Array(2)
// 0: {a: 1}
// 1: {a: 2}
array2.push({a:3});
console.log(JSON.parse(JSON.stringify(array)));
// 配列はOK
// Array(2)
// 0: {a: 1}
// 1: {a: 2}
array2[1].a = 4;
console.log(JSON.parse(JSON.stringify(array)));
// 中のオブジェクトもディープコピーしている
// Array(2)
// 0: {a: 1}
// 1: {a: 2}
console.log(JSON.parse(JSON.stringify(array2)));
// Array(3)
// 0: {a: 1}
// 1: {a: 4}
// 2: {a: 3}

Array.map()の中でオブジェクトをObject.assign()でディープコピーして返している。mapは新しい配列を返すので、配列のコピーもOK。

ところで、オブジェクトのディープコピーって

オブジェクトのディープコピーも調べるといくつか出てくる。
Object.assignってのがあるんだけど、Object.assignはシャローコピーなのでJSON.parse/JSON.stringifyって人もいれば、JSON.parse/JSON.stringifyは安易に使うなObject.assignがディープコピーだからそっちを使えっていう堂々巡りで、というか読んでいるとどっちもどっち感が強くて、状況に合わせて自力でどうにかするしかないじゃんっていう……。

おわりに

もっと正しいスマートなやり方を教えてください。


  1. phpのcloneが配列に使えない(そして使う必要がない)のは知っているのだけれど、良い例が思い浮かばなかったのです。 ↩︎


hammhiko

恥を晒して生きていきます。

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!

ボードとは?

関連記事

コメント