こんにちは、フロントエンドエンジニアのまさにょんです。
今回は、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つ紹介します。
- Array.prototype.concat()で配列を「値渡し-Copy」する。
- Array.prototype.slice()で配列を「値渡し-Copy」する。
- スプレッド構文(Spread-Syntax)で配列をCopyする。
それでは順に見て行きましょう。
Array.prototype.concat()で配列をCopyする
concatメソッドは、2つ以上の配列を結合するために使用します。
このメソッドは既存の配列を変更せず、新しい配列を返す非破壊的メソッドです。
引数が省略された場合は、Methodを呼び出した配列のCopyを実行結果として返却します。
基本構文をまとめると、次のようになります。
構文: 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
自体は含めず、その直前まで取り出します。例えば、
MDN: Array.prototype.slice()slice(1,4)
は 2 番目から 4 番目までの要素 (インデックスがそれぞれ 1, 2, 3 番目の要素) を取り出します。
ちなみに、引数を指定しない場合は、配列すべてをsliceして返却するので結果としてCopyとなります。
基本構文をまとめると、次のようになります。
構文: 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. 初級者向け
参考・引用
- MDN: Array.prototype.concat()
- MDN: Array.prototype.slice()
- MDN: スプレッド構文
- [JavaScript] Arrayメソッド破壊・非破壊チートシート