設定を覚えた関数をつくる
このレッスンで分かること
- 設定値を先に受け取り、あとから何度でも使える関数を返すやりかた
- 毎回引数で渡す書きかたと、設定を焼き込む書きかたの違い
- filter や map に渡す判定を、工場関数から作り分ける手順
設定を覚えた関数をつくる とは
しきい値や単位のような設定を引数で受け取り、その設定を覚えた関数を戻り値として返すやりかたです。設定を書くのは 1 回だけになり、使う側は値を渡すだけで済みます。
同じ条件を何度も書いている
第2章で作った絞り込みを思い出します。表示回数が一定以上の作品だけを取り出す処理です。
const popular = works.filter(function (work) {
return work.views >= 100;
});
const middle = works.filter(function (work) {
return work.views >= 30;
});やっていることは同じで、違うのは 100 と 30 という数字だけです。この形のまま条件が増えると、同じ 3 行が数字だけ変えて並びます。あとで「表示回数ではなく公開済みも見る」となったとき、直す場所が並んだ数だけ増えます。
コピーした 2 つの塊のうち、違いが数字ひとつだけなら、その数字は引数にできます。引数にできるものを、コピーで済ませないようにします。
数字を引数にした関数を返す
判定そのものを作る関数を用意します。しきい値を受け取り、しきい値を覚えた判定関数を返す形です。
function createMinViewsFilter(minViews) {
return function (work) {
return work.views >= minViews;
};
}
const isPopular = createMinViewsFilter(100);
const isMiddle = createMinViewsFilter(30);
console.log(works.filter(isPopular).length);
console.log(works.filter(isMiddle).length);createMinViewsFilter(100) の戻り値は関数です。その関数は minViews が 100 だったことを覚えているので、あとは work を渡すだけで判定してくれます。前のレッスンのカウンタと仕組みは同じで、閉じ込めている値が「書き換える状態」ではなく「変わらない設定」になっただけです。
isPopular は filter にそのまま渡せます。関数を受け取る側から見ると、ただの引数 1 つの関数に見えるので、filter でも find でも every でも同じように使えます。
| 書きかた | 設定を書く回数 | 呼び出しの形 |
|---|---|---|
| 毎回その場で書く | 使う場所の数だけ | filter で無名関数を毎回書く |
| 引数を 2 つ取る関数 | 呼ぶたびに 1 回 | 自分でループを書くことになる |
| 設定を覚えた関数を返す | 作るときの 1 回だけ | filter にそのまま渡せる |
3 行目だけが、設定を書く場所と使う場所を分けられています。
設定は数字でなくてもよい
閉じ込めるのは数値に限りません。単位や接頭辞のような文字列も同じように扱えます。
function createLabeler(unit, suffix) {
return function (value) {
return value + unit + suffix;
};
}
const viewLabel = createLabeler(" 回", "表示");
const yearLabel = createLabeler(" 年", "");
console.log(viewLabel(120)); // 120 回表示
console.log(yearLabel(2026)); // 2026 年map と組み合わせると、配列全体の見え方を 1 行で切り替えられます。
const views = [120, 48, 32];
console.log(views.map(viewLabel));
// ["120 回表示", "48 回表示", "32 回表示"]map に渡すのは viewLabel という名前だけです。単位を変えたくなったら createLabeler に渡す文字列を直すだけで、map の行はそのままで済みます。
設定を焼き込んだ関数は、名前が仕様書になります。isPopular や viewLabel という名前が読めれば、中身を開かなくても何をするか分かります。無名関数を並べたコードには、この手がかりがありません。
設定をまとめて渡す
設定が 2 つ 3 つと増えてきたら、引数を並べるよりオブジェクトで受け取るほうが読みやすくなります。
function createWorkFilter(options) {
const minViews = options.minViews;
const onlyPublished = options.onlyPublished;
return function (work) {
if (onlyPublished && !work.published) {
return false;
}
return work.views >= minViews;
};
}
const isFeatured = createWorkFilter({ minViews: 100, onlyPublished: true });
console.log(works.filter(isFeatured).length);呼ぶ側は minViews がどれで onlyPublished がどれかを名前で書けるので、引数の順番を覚える必要がありません。工場の中で設定を取り出しておけば、返す関数の中は判定だけになります。
設定の取り出しを工場の側でやっておくのがこつです。返した関数は作品の数だけ呼ばれるので、毎回同じ値を options から読み直す理由はありません。
しきい値の一覧から作り分ける
設定が配列で来る場合も同じです。しきい値の一覧を回して、そのつど工場を呼びます。
const thresholds = [0, 30, 100];
const counts = thresholds.map(function (threshold) {
const isEnough = createMinViewsFilter(threshold);
return works.filter(isEnough).length;
});
console.log(counts); // [5, 3, 1]しきい値ごとに別の判定関数ができ、それぞれが自分の threshold を覚えています。工場を呼ぶ回数だけ設定の箱ができるのは、前のレッスンのカウンタと同じです。今回の課題はこの形をそのまま書きます。
よくある間違い
ひとつめは、返すのを忘れて中で判定してしまうことです。
function createMinViewsFilter(minViews) {
return function (works) {
return works.filter(function (work) {
return work.views >= minViews;
});
};
}これは配列を受け取る関数になっているので、filter にそのまま渡すと 1 件ずつの work が配列として扱われて壊れます。返す関数の引数は、渡し先が呼ぶときの形にそろえます。
ふたつめは、工場の外で設定を持ってしまうことです。
let minViews = 100;
function createMinViewsFilter() {
return function (work) {
return work.views >= minViews;
};
}これだと 2 種類の判定を同時に持てません。あとから minViews を書き換えると、先に作った関数の動きまで変わります。
みっつめは、工場を毎回の判定の中で呼んでしまうことです。
works.filter(function (work) {
return createMinViewsFilter(100)(work);
});動きはしますが、要素の数だけ関数を作り直しています。工場は filter の外で 1 回呼び、できた関数を渡します。
要件
- 関数名は countWorksByThreshold とし、引数は works (作品の配列) と thresholds (数値の配列) の 2 つにする
- 関数の中で、しきい値を受け取って判定関数を返す工場関数を定義する。判定関数は作品 1 件を受け取り true か false を返す
- 判定は work.views がしきい値以上のときに true とする。しきい値ちょうどの作品も数える
- 戻り値は thresholds と同じ長さの数値の配列にする。works が空なら件数はすべて 0 になる
入出力例
countWorksByThreshold([{"id":1,"title":"プロフィールサイト","views":120},{"id":2,"title":"自己紹介カード","views":48},{"id":3,"title":"スキル一覧の表","views":32},{"id":4,"title":"作品ギャラリー","views":8},{"id":5,"title":"学習ログ","views":0}], [0,30,100]) → [5,3,1]
countWorksByThreshold([{"id":1,"title":"プロフィールサイト","views":120},{"id":2,"title":"自己紹介カード","views":48},{"id":3,"title":"スキル一覧の表","views":32},{"id":4,"title":"作品ギャラリー","views":8},{"id":5,"title":"学習ログ","views":0}], [8]) → [4]
countWorksByThreshold([{"id":1,"title":"プロフィールサイト","views":120},{"id":2,"title":"自己紹介カード","views":48},{"id":3,"title":"スキル一覧の表","views":32},{"id":4,"title":"作品ギャラリー","views":8},{"id":5,"title":"学習ログ","views":0}], [200]) → [0]
countWorksByThreshold([], [0,50]) → [0,0]
countWorksByThreshold([{"id":1,"title":"プロフィールサイト","views":120},{"id":2,"title":"自己紹介カード","views":48},{"id":3,"title":"スキル一覧の表","views":32},{"id":4,"title":"作品ギャラリー","views":8},{"id":5,"title":"学習ログ","views":0}], []) → []