tag:crieit.net,2005:https://crieit.net/tags/%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%82%B3%E3%83%94%E3%83%BC/feed
「ディープコピー」の記事 - Crieit
Crieitでタグ「ディープコピー」に投稿された最近の記事
2019-11-29T22:48:04+09:00
https://crieit.net/tags/%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%82%B3%E3%83%94%E3%83%BC/feed
tag:crieit.net,2005:PublicArticle/15566
2019-11-29T22:48:04+09:00
2019-11-29T22:48:04+09:00
https://crieit.net/posts/JavaScript
JavaScript:オブジェクトが中に入った配列をコピーする
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>こんなの、絶対みんな直面していると思うんだけど、調べても出てこないのは、ググり方が悪いのか、需要がないのか、みんな余裕でやってしまうのか、なんなんだ?</p>
<h2 id="想定読者"><a href="#%E6%83%B3%E5%AE%9A%E8%AA%AD%E8%80%85">想定読者</a></h2>
<p>下記の挙動を理解している人</p>
<pre><code class="JavaScript">const array = [1,2,3];
const array2 = array;
console.log(array); // → [1,2,3]
array2[1] = 4;
console.log(array); // → [1,4,3]
</code></pre>
<h2 id="配列をディープコピーしたい"><a href="#%E9%85%8D%E5%88%97%E3%82%92%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%82%B3%E3%83%94%E3%83%BC%E3%81%97%E3%81%9F%E3%81%84">配列をディープコピーしたい</a></h2>
<p>配列をコピーして別物として扱いたいとき、 = で代入してはいけないのは流石に分かるのだけど、殊更JavaScriptになると「あれ、どうやるんだっけ?」と毎回調べるハメになる。<br />
しかも、やれ<a target="_blank" rel="nofollow noopener" href="https://qiita.com/takahiro_itazuri/items/882d019f1d8215d1cb67">sliceを使え、concatを使え</a>だの、やれ<a target="_blank" rel="nofollow noopener" href="https://pisuke-code.com/js-correct-way-of-array-copy/">Array.fromを使え</a>だの、様々な方法があって、かつ、そいつら、ディープコピーのメソッドじゃないよね? <a target="_blank" rel="nofollow noopener" href="https://www.php.net/manual/ja/language.oop5.cloning.php">clone</a>とかないの?<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> という状況なのですが、文句を言っても仕方がないので、愚直にやるわけです。</p>
<h2 id="配列の中身がオブジェクトだとうまくいかない"><a href="#%E9%85%8D%E5%88%97%E3%81%AE%E4%B8%AD%E8%BA%AB%E3%81%8C%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%A0%E3%81%A8%E3%81%86%E3%81%BE%E3%81%8F%E3%81%84%E3%81%8B%E3%81%AA%E3%81%84">配列の中身がオブジェクトだとうまくいかない</a></h2>
<p>で、こんなコードを書いたわけですが、</p>
<pre><code class="JavaScript">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}
</code></pre>
<p>目を疑った。あれ、実はsliceはダメなのかな? とか思ってconcatもArray.fromも試したけどダメ。<br />
1時間くらい悩んで、「ああ、配列はディープコピーされたけど、中のオブジェクトが参照のままなのか」と気がついた。</p>
<pre><code class="JavaScript">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}
</code></pre>
<h2 id="解決策"><a href="#%E8%A7%A3%E6%B1%BA%E7%AD%96">解決策</a></h2>
<p>え、これどーすんの? と少し悩んで、下記の様にした。</p>
<pre><code class="JavaScript">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}
</code></pre>
<p>Array.map()の中でオブジェクトをObject.assign()でディープコピーして返している。mapは新しい配列を返すので、配列のコピーもOK。</p>
<h2 id="ところで、オブジェクトのディープコピーって"><a href="#%E3%81%A8%E3%81%93%E3%82%8D%E3%81%A7%E3%80%81%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%82%B3%E3%83%94%E3%83%BC%E3%81%A3%E3%81%A6">ところで、オブジェクトのディープコピーって</a></h2>
<p>オブジェクトのディープコピーも調べるといくつか出てくる。<br />
<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Examples">Object.assign</a>ってのがあるんだけど、<a target="_blank" rel="nofollow noopener" href="https://kuroeveryday.blogspot.com/2017/05/deep-clone-object-in-javascript.html">Object.assignはシャローコピーなのでJSON.parse/JSON.stringify</a>って人もいれば、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/seihmd/items/74fa9792d05278a2e898">JSON.parse/JSON.stringifyは安易に使うな</a>、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/seihmd/items/74fa9792d05278a2e898#comment-7b97f37d119fb69712f2">Object.assignがディープコピーだからそっちを使え</a>っていう堂々巡りで、というか読んでいるとどっちもどっち感が強くて、状況に合わせて自力でどうにかするしかないじゃんっていう……。</p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>もっと正しいスマートなやり方を教えてください。</p>
<div class="footnotes" role="doc-endnotes">
<hr />
<ol>
<li id="fn:1" role="doc-endnote">
<p>phpのcloneが配列に使えない(そして使う必要がない)のは知っているのだけれど、良い例が思い浮かばなかったのです。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div>
hammhiko