いきなりですが問題です。
以下のような処理を実装する際、どんな風に実装しますか?
・A,B,C,Dという文字列を含む配列を含む多次元配列を作る
・多次元配列中の二次元配列要素(ABCD)の順序は、1次元要素ことにバラバラである
やりがちなパターン
コードで書くと、以下のような感じで書く方が多いんじゃないでしょうか。
const list = [1, 2, 3, 4];
const result = [];
for (let i=0; i < 10; i++) {
const item = list.sort(() => 0.5 - Math.random());
result.push(item);
}
console.log(result);
しかし、実際にやってみると、これでは冒頭の条件を満たせていません。
// 以下、上記コードを実行した結果
(10) [Array(4), Array(4), Array(4), Array(4), Array(4), Array(4), Array(4), Array(4), Array(4), Array(4)]
0: (4) [4, 2, 3, 1]
1: (4) [4, 2, 3, 1]
2: (4) [4, 2, 3, 1]
3: (4) [4, 2, 3, 1]
4: (4) [4, 2, 3, 1]
// 以下略
pushした配列の二次元配列の順序が、同じになってしまっています。
itemをconsole.logで出力するとわかりますが、ループごとでランダムシャッフルされています。
正解
const list = [1, 2, 3, 4];
const result = [];
for (let i=0; i < 10; i++) {
const item = list.sort(() => 0.5 - Math.random());
result.push(item.concat());
}
console.log(result);
↑のようにpush時にconcat()を追加することで1配列要素ごとに内部の要素がバラバラば配列を作ることができます。
(10) [Array(4), Array(4), Array(4), Array(4), Array(4), Array(4), Array(4), Array(4), Array(4), Array(4)]
0: (4) [4, 2, 1, 3]
1: (4) [3, 1, 2, 4]
2: (4) [4, 2, 1, 3]
3: (4) [4, 2, 1, 3]
4: (4) [3, 4, 2, 1]
なぜconcat()を足す必要があるのかというと、pushメソッドでは配列は参照渡しで要素が追加されるからです。
pushメソッドの参照渡しについて
参照渡しとは「関数へ変数の値を記憶しているメモリ領域を渡す」ということです。
これに対して値渡し(関数へ変数の値を渡し、変数の記憶領域はそのまま)というのがあります。
参照渡しについてご存知の方はこのまま読み進めてもらうとして、
分かりやすく示したgifがvue3リファレンスページにあったので、よく分からないという方はイメージを掴むためにチェックするといいかも↓
Vu3リファレンス:ref によるリアクティブな変数
push()が参照渡しであるということは、
forループで毎回個別に作成していたつもりだった配列(item)は、list内に追加済の配列要素でもあるという状態だっため、
新しい配列を返却するconcat()を使ってあげる必要があるのでした。
参照渡し・値渡しになるかは引数の型次第
配列の他にも、Object,クラス
をpushに渡した場合も参照渡しになります。
こちらも必要に応じて新しいObject,インスタンスをpush()に渡すなどの対応をするようにしましょう。
参考