千里の道も1commitから

関西在住のWebエンジニアです。長らくブログをサボっていたので、1日1記事、1commitを目標にゆるりと頑張ります。積み重ねが研鑽となると信じて

JavaScript 再入門 配列コピー

JavaScriptの配列・オブジェクトのコピーはちょっとややこしいです。
業務のコードでも間違った(もしくは考慮されていない)使い方をしているのを見かけるし、
自分も脳筋 ... or _.cloneDeep は良くないので今一度思考を整理したいと思います。

コピーの種類

JavaScriptのコピーでは第一レベルまでコピーする浅いコピー(Shallow Copy)とネストしたオブジェクトまでコピーする2つのコピーがあります。
この第一階層までってのが肝で、 concatはダメでspreadは大丈夫。みたいな主張をたまに聞く結果となります。第一階層とは以下のようなネストしてない配列となります。

let family = ['わい', '弟', '妹', 'おとん', 'おかん', ['嫁', '義妹']];
let newFamily = [];

// 配列処理
newFamily = [...family];
newFamily[0] = 'ぼく';
newFamily[5][0] = 'ぼく嫁';
console.log(family);
console.log(newFamily);

// 結果
[ 'わい', '弟', '妹', 'おとん', 'おかん', [ 'ぼく嫁', '義妹' ] ]
[ 'ぼく', '弟', '妹', 'おとん', 'おかん', [ 'ぼく嫁', '義妹' ] ] // ネストした配列はメモリ参照となる

それでいてJavaScriptではコピーできる方法が多いので、それが理解の妨げに拍車をかけてる気がします。

浅すぎるコピー

同じ参照を持つ。コピー用途で使ってたらしばくやつです。

  let family = ['わい', '弟', '妹', 'おとん', 'おかん', ['嫁', '義妹']];
  let newFamily = family;

浅いコピー

第一階層までは完全に別の

Array.from(['a', 'b', 'c']);

配列風オブジェクトや反復可能オブジェクトから、新しい、浅いコピーの Array インスタンスを生成します。 ここでは省きますが、配列風というのが特徴

  let family = ['わい', '弟', '妹', 'おとん', 'おかん', ['嫁', '義妹']];
  // 配列を複製
  let newFamily = Array.from(family);

Array.from() - JavaScript | MDN

['a', 'b', 'c'].slice(1, 2)

begin から end まで選択された配列の一部をシャローコピーして、新しい配列オブジェクトを返します。
名前の通り切り出し箇所をbeginとendで指定できるのがポイント

  let family = ['わい', '弟', '妹', 'おとん', 'おかん', ['嫁', '義妹']];
  // 配列を複製
  let newFamily = arr.slice(2,4);

Array.prototype.slice() - JavaScript | MDN

spread構文

ES6から入ったあれ
スプレッド構文を使うと、配列式や文字列などの反復可能オブジェクトを、 0 個以上の引数 (関数呼び出しの場合) や要素 (配列リテラルの場合) を期待された場所で展開したり、オブジェクト式を、 0 個以上のキーと値のペア (オブジェクトリテラルの場合) を期待された場所で展開したりすることができます。

airbも配列コピーに推奨している。確かに配列のみならスプレッドは確かにわかりやすいか〜

  let family = ['わい', '弟', '妹', 'おとん', 'おかん', ['嫁', '義妹']];
  // 配列を複製
  let newFamily = [...family];

spread構文はこの使い方が最強。

  const numbers = [1, 2, 3];
  const str = "abc";
  console.log([...numbers, ...str]));

  // 6

深いコピー

ネストした階層まで完全に別オブジェクトを作りだす深いコピーです。 シャローコピーよりはコストがかかるのでネストをしないなら不要。

JSON.parse(JSON.stringify([1, 2, 3]))

昔からあるTips。Date型が上手く変換できなかったかも。完全にコピーするなら _.cloneDeepが良いかなぁ。

  let family = ['わい', '弟', '妹', 'おとん', 'おかん', ['嫁', '義妹']];
  let newFamily = JSON.parse(JSON.stringify(family));

_.cloneDeep

現在最強ディープコピー。lodashの一関数遅いと聞くので、もっと早いものがあれば是非...

  const _ = require('lodash');
  let family = ['わい', '弟', '妹', 'おとん', 'おかん', ['嫁', '義妹']];
  let newFamily = _.cloneDeep(family);

lodash.com

TODO: _.cloneDeepの詳細を追う

所感

シャローコピーならスプレッド、ディープコピーなら _.cloneDeep
各種メソッドの整理になってよかったです。良いJavaScriptライフを

1日1コミット

JS_Study/array_copy.js at b820b5d77376ed70c005b86363c0b0ff4c45b9d8 · gonta616/JS_Study · GitHub

参考

yukimonkey.com stackoverflow.com medium.com