【JavaScript入門】配列をCopyする方法 | concat・slice・スプレッド構文

Array-Copy

こんにちは、フロントエンドエンジニアのまさにょんです。

今回は、JavaScriptで、配列をCopyする方法について解説して行きます。

参照渡し-Copyの問題点

配列やObjectをCopyする時に、問題になってくるのが、そのCopyが「参照渡しのCopy」なのか、「値渡しのCopy」なのかと言う点です。

「値渡し」はデータを直接やり取りするのに対して、「参照渡し」はデータの保管場所を共有するやり取りです。

なので、「値渡しのCopy」なら問題ないのですが、「参照渡しのCopy」の場合は、データの保管場所を共有しているので、どちらかを変更すると、もう一方も変更されてしまいます。

これをCode上で確認すると次のようになります。

// < 参照渡しの問題点 >

const devRobotama = ['ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機'];

// 1. 「参照渡し」(Pass by reference)のままCopy
const referenceCopy = devRobotama;
console.log({referenceCopy});
// referenceCopy: (4) ['量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機']

// 2. shift() => Copyした配列から先頭の値を削除する(破壊的メソッド)
referenceCopy.shift();

console.log({referenceCopy});
// referenceCopy: (4) ['量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機']

// 3. Copy元の配列も値が削除されている!
console.log({devRobotama});
// devRobotama: (4) ['量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機']

// このような思わぬCopy元配列の変更が、参照渡し-Copyの問題点

上記のSample-Codeのように、「参照渡し」(参照元の共有)による思わぬ配列の変更が「参照渡し-Copy」の問題点です。

「参照渡し-Copy」の問題点がわかったところで、「値渡し-Copy」の実装方法を確認して行きましょう。

この記事では、配列の「値渡し-Copy」の方法として3つ紹介します。

  1. Array.prototype.concat()で配列を「値渡し-Copy」する。
  2. Array.prototype.slice()で配列を「値渡し-Copy」する。
  3. スプレッド構文(Spread-Syntax)で配列をCopyする。

それでは順に見て行きましょう。

Array.prototype.concat()で配列をCopyする

concatメソッドは、2つ以上の配列を結合するために使用します。

このメソッドは既存の配列を変更せず、新しい配列を返す非破壊的メソッドです。

引数が省略された場合は、Methodを呼び出した配列のCopyを実行結果として返却します。

基本構文をまとめると、次のようになります。

Array.prototype.concat()-基本構文
構文: array.concat(mergeArray);

引数:  mergeArray
 => 新しい配列に連結する配列や値です。

実行結果(返値): 新しい Array インスタンス。
// < concat() を使った 値渡し-Array-Copy >

const robotamaList = ['ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機'];

// 1. concat()で「値渡し-Copy」(Pass by value)
const copyRobotamaList = robotamaList.concat();

console.log(JSON.stringify(copyRobotamaList));
// ["ロボ・ボール","量産型・ロボ玉","ロボ玉試作1号機","ロボ玉試作2号機","ロボ玉試作3号機"]

// 2. unshift() => Copyした配列の先頭に要素を追加する(破壊的メソッド)
copyRobotamaList.unshift('ロボ玉-Mk2');

console.log({copyRobotamaList});
// copyRobotamaList: (6) ['ロボ玉-Mk2', 'ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機']

// 3.「値渡し-Copy」(Pass by value)なので、元の配列は操作されていない。
console.log({robotamaList});
// robotamaList: (5) ['ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機']

Array.prototype.slice()で配列をCopyする

sliceメソッドは、startNum と endNum で配列の切り取り範囲を指定して、既存の配列からsliceした新しい配列を作成することができます。

startNum から endNum まで ( endNum は含まれない) で選択された範囲を切り取った(sliceした)新しい配列オブジェクトを実行結果として返却します。

また、sliceメソッドは元の配列は変更されない非破壊的メソッドなので、元の配列は変更されません。

注意点として、endNumは、そのindex番号の直前までを切り取るような仕様なので注意が必要です。

slice は end 自体は含めず、その直前まで取り出します。

例えば、slice(1,4) は 2 番目から 4 番目までの要素 (インデックスがそれぞれ 1, 2, 3 番目の要素) を取り出します。

MDN: Array.prototype.slice()

ちなみに、引数を指定しない場合は、配列すべてをsliceして返却するので結果としてCopyとなります。

基本構文をまとめると、次のようになります。

Array.prototype.slice()-基本構文
構文: array.slice(startNum, endNum);

引数: startNum, endNum
1. startNum: 取り出しの開始位置を示す 0 から始まるインデックスです。
2. endNum: 取り出しを終える直前の位置を示す 0 から始まるインデックスです。

実行結果(返値): 取り出された要素を含む新しい配列です。
// < slice() を使った 値渡し-Array-Copy >

const robotamaArray = ['ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機'];

// 1. slice()で「値渡し-Copy」(Pass by value)
const copyRobotamaArray = robotamaArray.slice();
console.log(copyRobotamaArray);
// ['ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機']

// 2. reverse() => 配列の順序を逆にする(破壊的メソッド)
copyRobotamaArray.reverse();
console.log({copyRobotamaArray});
// copyRobotamaArray: (5) ['ロボ玉試作3号機', 'ロボ玉試作2号機', 'ロボ玉試作1号機', '量産型・ロボ玉', 'ロボ・ボール']

// 3.「値渡し-Copy」(Pass by value)なので、元の配列は操作されていない。
console.log({robotamaArray});
// robotamaArray: (5) ['ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機']

スプレッド構文(Spread-Syntax)で配列をCopyする

「スプレッド構文」(Spread-Syntax)は、ArrayやObjectを展開するのに使える構文です。

「 … 」の後にArrayやObjectを記述することで、その中身を展開します。

スプレッド構文は、元のArrayやObjectには変更を加えない非破壊的な記法なので、Copyに使えます。

// < Spread-Syntax(スプレッド構文)を使った 値渡し-Array-Copy >

const robotamaDev = ['ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機'];

// 1. Spread-Syntaxで「値渡し-Copy」(Pass by value)
const copyRobotamaDev = [...robotamaDev];
console.log(copyRobotamaDev); 
// ['ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機']

// 2. push() => 配列の順序を逆にする(破壊的メソッド)
copyRobotamaDev.push('Z-ロボ玉');
console.log({copyRobotamaDev}); 
// copyRobotamaDev: (6) ['ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機', 'Z-ロボ玉']

// 3.「値渡し-Copy」(Pass by value)なので、元の配列は操作されていない。
console.log({robotamaDev}); 
// robotamaDev: (5) ['ロボ・ボール', '量産型・ロボ玉', 'ロボ玉試作1号機', 'ロボ玉試作2号機', 'ロボ玉試作3号機']

以上、3つの配列をCopyする方法、concat(), slice(), スプレッド構文の解説でした。

ぜひ活用してみてください。

JavaScript書籍 Ver. 中級-上級者向け

JavaScript書籍 Ver. 初級者向け

参考・引用

  1. MDN: Array.prototype.concat()
  2. MDN: Array.prototype.slice()
  3. MDN: スプレッド構文
  4. [JavaScript] Arrayメソッド破壊・非破壊チートシート

最近の投稿