かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript 配列のコピー

JavaScript の配列は参照なので、破壊的な変更を加えてしまうと副作用を発生させるので取り扱いには注意 (危機管理) が必要です。
副作用を発生させないために配列操作を行う時はコピーを行うことが多いですが、配列が入れ子だったり、配列の中にオブジェクトを入れたりということも多いと思うので、改めて浅いコピー (shallow copy) と深いコピー (deep copy) について試したのでメモ

配列は参照

別の変数に代入してもコピーされるわけではない (同じデータを参照しているだけ)

const array = [
  {name: 'Shamiko'},
  {name: 'Chiyoda Momo'},
];

const copyArray = array;
// 配列を代入した変数と元の配列は同一 (同じデータを参照している)
console.log(copyArray === array);
// => true
copyArray.push({name: 'Hinatsuki Mikan'});
copyArray[0].name = 'Ririsu';
console.log(copyArray);
// => [{name: 'Ririsu'}, {name: 'Chiyoda Momo'}, {name: 'Hinatsuki Mikan'}]
console.log(array);
// => [{name: 'Ririsu'}, {name: 'Chiyoda Momo'}, {name: 'Hinatsuki Mikan'}]

slice() でコピー

const array = [
  {name: 'Shamiko'},
  {name: 'Chiyoda Momo'},
];

const copyArray = array.slice();
// コピーされているので内容は同じでも同一ではなくなっている
console.log(copyArray === array);
// => false
copyArray.push({name: 'Hinatsuki Mikan'});
copyArray[0].name = 'Ririsu';
console.log(copyArray);
// => [{name: 'Ririsu'}, {name: 'Chiyoda Momo'}, {name: 'Hinatsuki Mikan'}]
console.log(array);
// => [{name: 'Ririsu'}, {name: 'Chiyoda Momo'}]

Array.slice()shallow copy
ref. Array.prototype.slice() - JavaScript | MDN

スプレッド構文 ( […Array] ) でコピー

const array = [
  {name: 'Shamiko'},
  {name: 'Chiyoda Momo'},
];

const copyArray = [...array];
console.log(copyArray === array);
// => false
copyArray.push({name: 'Hinatsuki Mikan'});
copyArray[0].name = 'Ririsu';
console.log(copyArray);
// =>[{name: 'Ririsu'}, {name: 'Chiyoda Momo'}, {name: 'Hinatsuki Mikan'}]
console.log(array);
// => [{name: 'Ririsu'}, {name: 'Chiyoda Momo'}]

スプレッド構文 [...Array]shallow copy
ref.

Array.from() でコピー

const array = [
  {name: 'Shamiko'},
  {name: 'Chiyoda Momo'},
];

const copyArray = Array.from(array);
console.log(copyArray === array);
// => false
copyArray.push({name: 'Hinatsuki Mikan'});
copyArray[0].name = 'Ririsu';
console.log(copyArray);
// =>[{name: 'Ririsu'}, {name: 'Chiyoda Momo'}, {name: 'Hinatsuki Mikan'}]
console.log(array);
// => [{name: 'Ririsu'}, {name: 'Chiyoda Momo'}]

Array.from()shallow copy ref. Array.from() - JavaScript | MDN

Object.assign() でコピー

const array = [
  {name: 'Shamiko'},
  {name: 'Chiyoda Momo'},
];

const copyArray = Object.assign([], array);
console.log(copyArray === array);
// => false
copyArray.push({name: 'Hinatsuki Mikan'});
copyArray[0].name = 'Ririsu';
console.log(copyArray);
// =>[{name: 'Ririsu'}, {name: 'Chiyoda Momo'}, {name: 'Hinatsuki Mikan'}]
console.log(array);
// => [{name: 'Ririsu'}, {name: 'Chiyoda Momo'}]

Object.assign()shallow copy
ref. Object.assign() - JavaScript | MDN

JSON.stringify() で文字列に変換してコピー

JSON.stringify() で文字列に変換して、JSON.parse() で再び配列に戻す方法

const array = [
  {name: 'Shamiko'},
  {name: 'Chiyoda Momo'},
];

const copyArray = JSON.parse(JSON.stringify(array));
console.log(copyArray === array);
// => false
copyArray.push({name: 'Hinatsuki Mikan'});
copyArray[0].name = 'Ririsu';
console.log(copyArray);
// =>[{name: 'Ririsu'}, {name: 'Chiyoda Momo'}, {name: 'Hinatsuki Mikan'}]
console.log(array);
// => [{name: 'Shamiko'}, {name: 'Chiyoda Momo'}]

JSON.stringify() で一度文字列にしてしまえば、deep copy ができる

※ 但し、JSON.stringify()でのコピーは、Date オブジェクトや function など 文字列化すると壊れるオブジェクトが含まれていると正しくコピーできない
const array = [
  new Date(),
  () => { console.log('function') },
];

const copyArray = JSON.parse(JSON.stringify(array));
console.log(copyArray);
// => [ '2019-11-29T10:47:09.941Z', null ]
console.log(array);
// => [ 2019-11-29T10:47:09.941Z, [Function] ]

文字列化した際に、オブジェクトが壊れてしまう

lodash#cloneDeep でコピー

lodash ライブラリに含まれる cloneDeep メソッドを使ってコピー

const _ = require('lodash');
const array = [
  {name: 'Shamiko'},
  {name: 'Chiyoda Momo'},
  new Date(),
  {func() {console.log('foo');}
];

const copyArray = _.cloneDeep(array);
// => false
copyArray.push({name: 'Hinatsuki Mikan'});
copyArray[0].name = 'Ririsu';
console.log(copyArray);
// => [{name: 'Ririsu'}, {name: 'Chiyoda Momo'}, 2019-11-29T10:57:00.595Z, {func: [Function: func]}, {name: 'Hinatsuki Mikan'}]
console.log(arg);
// => [{name: 'Shamiko'}, {name: 'Chiyoda Momo'}, 2019-11-29T10:57:00.595Z, {func: [Function: func]}]

copyArray[2].getFullYear(); // => 2019
copyArray[3].func(); // => "foo"

lodash#cloneDeepdeep copy で、オブジェクトもコピーされる

まとめ

  1. JavaScriptの配列をコピーできる方法は、ほぼ shallow copy なので、入れ子になっているデータの取り扱いには注意 (危機管理) が必要
  2. 文字列化できるデータしか入ってないことが確実な場合は、JSON.parse(JSON.stringify(Array))deep copy することが可能
  3. function などが含まれる配列を deep copy したい場合は、再帰などを使った独自の関数を作る必要がある
  4. ライブラリを使えるなら deep copylodash#cloneDeep を使うのが簡単

JavaScript たのしい!


[参考]

Node.jsデザインパターン 第2版

Node.jsデザインパターン 第2版

まちカドまぞく 3巻 (まんがタイムKRコミックス)

まちカドまぞく 3巻 (まんがタイムKRコミックス)

まちカドまぞくの3巻まじ尊さの塊なのでシャミ子の二期お願いしますッ