JavaScript 入門

JS 関数を戻り値として返す

他の関数から関数を返すことは JavaScript の強力な機能であり、関数型プログラミングの基礎でもあります。
これにより、関数の振る舞いをその作成時の コンテキスト(Context) に基づいてカスタマイズできる、動的で柔軟なコードを作成することが可能になります。
本章では「戻り値としての関数」という概念を探求し、それらを効果的に作成・活用する方法をデモンストレーションします。

1. 戻り値としての関数の理解

JavaScript において、関数は 第一級オブジェクト(First-Class Citizens) です。これは、関数を他の 変数(Variables) と全く同じように扱えることを意味します。関数を変数に代入したり、引数として他の関数に渡したり(前章で学習した通り)、値として他の関数から返したりすることができます。

ある関数が別の関数を返すとき、返された関数は作成された時の環境を「記憶」することができます。この概念は クロージャ(Closures) と密接に関連しています。

1.1 基本的な例

まずはシンプルな例から始めましょう。

function multiplier(factor) {
  // この関数は別の関数を返します。
  return function(x) {
    // 返された関数は、外部関数のスコープにある 'factor' を 'x' に掛け合わせます。
    return x * factor;
  };
}

// 5を掛ける関数を作成
const multiplyBy5 = multiplier(5);
// 10を掛ける関数を作成
const multiplyBy10 = multiplier(10);

// 返された関数を使用
console.log(multiplyBy5(3));  // 出力: 15
console.log(multiplyBy10(3)); // 出力: 30

この例では、multiplierfactor を引数として受け取り、新しい関数を返す関数です。返された関数は引数 x を受け取り、xfactor の積を返します。

ここで注目すべきは、multiplier 関数の実行がすでに終了しているにもかかわらず、multiplyBy5factor が 5 であることを、multiplyBy10factor が 10 であることを「記憶」している点です。これはクロージャの働きによるものですが、概念としては「専用の関数をオンデマンドで生成している」と理解すれば十分です。

1.2 より詳細な例

次に、異なる プレフィックス(Prefix) を持つ挨拶メッセージ(例:「Hello」, 「Good morning」, 「Good evening」)を生成する関数を作成してみましょう。

function createGreeter(greeting) {
  return function(name) {
    return `${greeting}, ${name}!`;
  };
}

const sayHello = createGreeter("Hello");
const sayGoodMorning = createGreeter("Good morning");

console.log(sayHello("Alice"));       // 出力: Hello, Alice!
console.log(sayGoodMorning("Bob"));    // 出力: Good morning, Bob!

ここでは、createGreetergreeting 文字列を入力として受け取り、関数を返します。返された関数は name を入力として受け取り、完全な挨拶メッセージを返します。これにより、異なる挨拶のバリエーションを持つ関数を簡単に量産できます。

1.3 条件分岐ロジックを含む例

外部関数の中で 条件分岐(Conditional Logic) を使用して、どの関数を返すかを決定することもできます。

function createValidator(type) {
  if (type === 'email') {
    return function(value) {
      // シンプルなメールアドレス検証用正規表現
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      return emailRegex.test(value);
    };
  } else if (type === 'phoneNumber') {
    return function(value) {
      // シンプルな電話番号検証用正規表現 (アメリカ形式)
      const phoneRegex = /^\d{3}-\d{3}-\d{4}$/;
      return phoneRegex.test(value);
    };
  } else {
    return function(value) {
      return true; // デフォルトのバリデーション:常に true を返す
    };
  }
}

const validateEmail = createValidator('email');
const validatePhone = createValidator('phoneNumber');
const validateAnything = createValidator('anything');

console.log(validateEmail("[email protected]")); // 出力: true
console.log(validateEmail("invalid-email"));   // 出力: false
console.log(validatePhone("123-456-7890")); // 出力: true
console.log(validatePhone("1234567890"));   // 出力: false
console.log(validateAnything("any value")); // 出力: true

この例では、createValidatortype 引数を受け取り、その型に応じたバリデーション関数を返します。これにより、ロジックをカプセル化した再利用可能なバリデーターを動的に生成できます。

2. 実践的な例とデモンストレーション

2.1 カウンター関数ファクトリの作成

これは戻り値としての関数の古典的な例であり、クロージャのデモとしてよく使われます。

function createCounter() {
  let count = 0; // 'count' は createCounter のスコープ内に閉じ込められます
  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getValue: function() {
      return count;
    }
  };
}

const counter1 = createCounter();
const counter2 = createCounter();

console.log(counter1.increment()); // 出力: 1
console.log(counter1.increment()); // 出力: 2
console.log(counter2.increment()); // 出力: 1  (counter2 は独立しています)
console.log(counter1.decrement()); // 出力: 1
console.log(counter1.getValue()); // 出力: 1

ここで、createCounter は 3 つの関数(incrementdecrementgetValue)を含むオブジェクトを返します。createCounter() を使って作成された各カウンターインスタンスは、外部からアクセスできない独立した count 変数を持っています。これは状態と振る舞いを カプセル化(Encapsulation) する強力な手法です。返されたオブジェクトは、内部状態との対話や修正を可能にするメソッドを公開しています。

2.2 戻り値としての関数によるカリー化 (Currying)

カリー化(Currying) とは、複数の引数を持つ関数を、それぞれが単一の引数を受け取る一連の関数に変換するテクニックです。他の関数から返される関数は、このカリー化を実装するために頻繁に利用されます。

function add(x) {
  return function(y) {
    return function(z) {
      return x + y + z;
    };
  };
}

const add5 = add(5);
const add5and10 = add5(10);
const result = add5and10(2);
console.log(result); // 出力: 17

// あるいは、すべての関数を一度に呼び出す:
console.log(add(5)(10)(2)); // 出力: 17

この例では、add はカリー化された関数であり、引数を一つずつ受け取って新しい関数を返し、すべての引数が揃うまで繰り返します。最後の関数がすべての引数の合計を返します。これは、特定のパラメータをプリセットした専用関数を作成する際に非常に便利です。