コード
function randomSelected(array, count) {
const copyArray = [...array];
const randomArray = [...Array(count)].map(() => {
const randomStartIndex = Math.floor(Math.random() * copyArray.length);
return copyArray.splice(randomStartIndex, 1).at();
})
return randomArray;
}
コードの解説
function randomSelected(array,count) { ・・・}
関数宣言でrandomSelected関数を定義しています。
第一引数にランダム表示対象の配列を指定します。
第二引数にランダム表示させたい数を指定します。
例:10個の中から3個ランダムに表示させる場合、count
には3を指定します。
const copyArray = [...array];
引数に渡された配列をスプレッド構文でコピーしています。
コピーしないと元の配列が破壊されてしまうためです↓
const original = ["りんご", "ぶどう", "スイカ"];
const copy = original;
copy[1] = "みかん";//ぶどうをみかんに変更
console.log(`original:${original}`); //-> original:'りんご', 'みかん', 'スイカ'
console.log(`copy:${copy}`);//-> copy:'りんご', 'みかん', 'スイカ'
上記の結果を見ると、コピー元の配列originalも「ぶどう」から「みかん」に変更されてしまってます。これを防ぐためにスプレッド構文で配列をコピーしています↓
const original = ["りんご", "ぶどう", "スイカ"];
const copy = [...original];//スプレッド構文でコピー
copy[1] = "みかん"; //ぶどうをみかんに変更
console.log(`original:${original}`); //-> original:'りんご', 'ぶどう', 'スイカ'
console.log(`copy:${copy}`); //-> copy:'りんご', 'みかん', 'スイカ'
スプレッド構文を使用すると、配列の長さ分だけ値を展開することができます↓
const array = ["ぶどう", "りんご", "みかん"];
array.length = 1;
console.log([...array]);//-> ["ぶどう"]
また、スプレッド構文で配列をコピーしても
コピー元の配列が破壊される場合があります↓
const original = ["りんご", ["ぶどう", "トマト"], "スイカ"];
const copy = [...original];
copy[1][1] = "みかん";//トマトをみかんに変更
console.log(`original:${original}`);//->original:りんご,ぶどう,みかん,スイカ
console.log(`copy:${copy}`);//->copy:りんご,ぶどう,みかん,スイカ
上記の結果を見ると、コピー元の配列originalの「トマト」が「みかん」に変更されています。
スプレッド構文でコピーした配列の階層が深くなっている値を変更すると、コピー元の配列が破壊されます。
階層が深くなった配列の値を変更してもコピー元が破壊されないようにするには、structuredClone()
を使用します↓
const original = ["りんご", ["ぶどう", "トマト"], "スイカ"];
const copy = structuredClone(original);
copy[1][1] = "みかん";
console.log(`original:${original}`);//->original:りんご,ぶどう,トマト,スイカ
console.log(`copy:${copy}`);//->copy:りんご,ぶどう,みかん,スイカ
structuredClone()
について↓
const randomArray = [...Array(count)].map( () => {・・・})
Array()
は、新しく配列を作成するときに使用します。配列リテラル記法で記述することもできますが、今回は配列の長さが count
の値によって変わり、動的なのでArray()
を使用してます。
リテラル:プログラム上で数値や文字列など、データ型の値を直接記述できるように構文として定義されたもの。”りんご”や1、など。
静的:固定されている、変動しない
動的:入力内容によって変動する
Array(count)
とすることで、count
に渡された値の長さのみを持つ空の配列が作成されます↓
//countに「3」が渡された場合
const array = Array(3);
console.log(array)//-> [empty * 3] length:3
空の配列のままでは反復処理できないので、角括弧[]でArray(count)
を囲み、
スプレッド構文で配列の長さ分だけ値を展開します↓
const array = [...Array(3)];
console.log(array);//-> [undefined,undefined,undefined]
スプレッド構文を使用せず、fill()
を使用しても同様の結果が得られます↓
const array = Array(3).fill();
console.log(array);//-> [undefined,undefined,undefined
map()
は、配列を反復処理し、その結果から新しい配列を返すメソッドです。
詳しくはこちら→Array.prototype.map() – JavaScript | MDN
forEach()
は反復処理を行うだけで、新しい配列を返さないためmap()
を使用しています。
上記からconst randomArray = [...Array(count)].map( () => {・・・})
をまとめると、count
の数だけ反復処理を行い、その結果から得られた新しい配列を定数randomArrayに代入するということになります。
const randomStartIndex = Math.floor(Math.random() * copyArray.length);
Math.floor()
は、与えられた数値の小数点以下を切り捨てるメソッドです↓
console.log(Math.floor(5.002));//-> 5
console.log(Math.floor(5.6));//-> 5
console.log(Math.floor(-5.6));//-> -6
Math.random()
は、0以上 (0は含む) 1未満 (1は含まない) の数値を返します↓
console.log(Math.random())//-> 0 (0は含む)から1未満 (1は含まない)の値
Math.random() * copyArray.length
はMath.random()
にcopyArrayの長さを掛けているので、0からcopyArrayの長さ未満の数値を返します↓
//copyArrayの長さが10の場合
console.log(Math.random() * 10);//-> 0 (0は含む)から10未満 (10は含まない)の値
そして、Math.floor()
で全体を囲っているので、小数点以下は切り捨てられます。
その値を定数randomStartIndexに代入しています↓
//copyArrayの長さが10の場合
const randomStartIndex = Math.floor(Math.random() * 10);
console.log(randomStartIndex);//-> 0から9までの値を返す
return copyArray.splice(randomStartIndex, 1).at();
splice()
は、開始位置から特定の長さ分の要素を切り取るメソッドです↓
//開始位置が0で、長さが1の場合
const array = ["ぶどう", "りんご", "みかん"];
console.log(array.splice(0, 1));//-> [ぶどう]
上記を踏まえるとcopyArray.splice(randomStartIndex, 1)
は、開始位置が0から9までの値を一つ切り取るということになります。
先ほども説明した通りmap()
は反復処理をして得た結果から新しい配列を返します。 copyArray.splice(randomStartIndex, 1)
は、長さ一つ分の配列を返します。
現状では配列の中に配列を返すので、階層が深くなります↓
//randomSelected関数のコード省略
//取り出した結果がみかんとりんごの場合
const array = ["ぶどう", "りんご", "みかん"];
const random = randomSelected(array, 2);
console.log(random);//-> [ ["みかん"], ["りんご"] ]
階層が深くなるのを防ぐためにat(
)メソッドを使用します。
at()
は、与えられた数の位置の値を取り出すためのメソッドです。
負の数が与えられると配列の末尾から数えます↓
const array = ["ぶどう", "りんご", "みかん"];
console.log(array.at());//-> ぶどう 何も数が与えられないと0番目の値を返す
console.log(arrya.at(-1))//-> みかん
console.log(array.at(-2));//-> りんご
よって、count
の数だけcopyArrayの中からランダムな位置の値を一つ取り出す処理を行い、その処理の結果から新しい配列を作成し定数randomArrayに返すということになります。
return randomArray;
randomSelected関数の返り値として、randomArrayを返しています。
定数randomAarrayに代入せず、いきなりreturn
で記述したほうがいいかもしれません。
使用例
Shibajukuのチーム制作で、プレゼント釣リーのゲーム中画面の実装を担当させていただきました。
プレゼントは全部で10個ありますが、
表示するのは5個なのでこのコードを使ってランダムに5個表示させました↓
const imagePath = "./assets/img/common/";
const imageClass = "p-playingGame__present js-present";
const presentBoxes = document.querySelectorAll(".js-presentBox");
const imageElements = [
`<img class="${imageClass}" src="${imagePath}common_book.svg" alt="">`,
`<img class="${imageClass}" src="${imagePath}common_cake.svg" alt="">`,
`<img class="${imageClass}" src="${imagePath}common_chicken.svg" alt="">`,
`<img class="${imageClass}" src="${imagePath}common_cookie.svg" alt="">`,
`<img class="${imageClass}" src="${imagePath}common_flower.svg" alt="">`,
`<img class="${imageClass}" src="${imagePath}common_switch.svg" alt="">`,
`<img class="${imageClass}" src="${imagePath}common_pc.svg" alt="">`,
`<img class="${imageClass}" src="${imagePath}common_shibatan.svg" alt="">`,
`<img class="${imageClass}" src="${imagePath}common_snowman.svg" alt="">`,
`<img class="${imageClass}" src="${imagePath}common_surprise_box.svg">`
];
function randomSelected(array, count) {
const copyArray = [...array];
const randomArray = [...Array(count)].map(() => {
const randomStartIndex = Math.floor(Math.random() * copyArray.length);
return copyArray.splice(randomStartIndex, 1).at();
})
return randomArray;
}
const randomImageElements = randomSelected(imageElements, 5);
presentBoxes.forEach((presentBox, index) => {
presentBox.insertAdjacentHTML("beforeend", randomImageElements[index]);
});
まとめ
数ヶ月後にはこのコードを忘れていそうだったので、メモとして残しておきます。
最後まで読んでいただきありがとうございました🙇♂️