はじめに
学習用のメモとして投稿します。
JavaScriptで練習問題を解く機会があり、その際にforEachメソッドの挙動で少し戸惑いました。
その原因と解決策について書いていきます。
問題の挙動
実際のものから少し変更していますが、練習問題の内容は次のようなものです。
- ユーザー名 username を引数に受け取り、権限が許可されているかどうかを判定する関数 checkPermission を実装してください。
- ユーザーの権限は、次のようなオブジェクトの配列 users に格納されています。
let users = [
{
username: '斉藤',
hasPermission: true
},
{
username: '齋藤',
hasPermission: false
},
];
▼呼び出し例
console.log(checkPermission('斉藤')) // true
console.log(checkPermission('齋藤')) // false
引数で与えられたユーザーが存在しない場合の処理など細かい点は置いておくとして、とりあえずforEachを使用すれば実装できるのではないかと思い、次のような実装を考えました。
function checkPermission(username) {
users.forEach(user => {
if (user.username == username) {
return user.hasPermission
}
})
}
console.log(checkPermission('斉藤'))// undefined
console.log(checkPermission('齋藤')) // undefined
想定では、true又はfalseが表示されるはずでしたが、実際に動かしてみると結果はどちらもundefinedでした。。
原因
真偽値をreturnしているのに、何故undefinedなのか、最初は理解できませんでしたが、公式ドキュメントに記載がありました。
forEach() は配列の各要素に対して callbackFn 関数を一度ずつ実行します。map() や reduce() と異なり、返値は常に undefined であり、チェーンできません。チェーンの最後に副作用を生じさせるのが典型的な使用法です。
メモ: 例外を発生する以外の方法で、forEach() ループを止めることはできません。ループ中に中断する必要がある場合、forEach() メソッドは適切な方法ではありません。
ループ中に中断できないということで、forEach内でbreakやcontinueを使用するのも適切ではないようです。
forEachは、配列の要素を取り出して、一つずつコールバックに渡しています。forEach内でreturnが使用されると、次の要素をコールバックに渡す処理に移ります。これはループ内でcontinueを使用したのと同様の挙動で、最後の要素をコールバックに渡した後、最終的にはundefindが返ってくることになります。
これを踏まえて、コードを次のように修正しました。
function checkPermission(username) {
const user = users.find(user => user.username === username)
if (user) {
return user.hasPermission;
} else {
return undefined;
}
}
console.log(checkPermission2('斉藤'))// true
console.log(checkPermission2('齋藤')) // false
これで想定通り動くようになりました。
他の言語ではあまりこのような挙動は見ないように思いますが(と言いつつそこまで詳しいわけではない)、、ちょっとクセがあるなと感じました。