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