JavaScriptの関数:定義と呼び出し

2025-07-28

関数の基本概念

関数はJavaScriptにおける重要な構成要素で、特定のタスクを実行するコードブロックです。関数を使うことで、コードの再利用性、可読性、保守性が向上します。

関数の利点

  • コードの再利用: 同じ処理を何度も書かなくて済む
  • モジュール化: プログラムを小さな機能単位に分割できる
  • 保守性向上: 変更が必要な場合、1箇所を修正するだけで済む

関数の定義方法

1. 関数宣言(Function Declaration)

// 関数宣言
function greet(name) {
  return `こんにちは、${name}さん!`;
}

// 呼び出し
console.log(greet('太郎')); // こんにちは、太郎さん!

特徴:

  • ホイスティングされる(定義前に呼び出せる)
  • ブロックスコープではなく関数スコープ

2. 関数式(Function Expression)

// 関数式
const greet = function(name) {
  return `こんにちは、${name}さん!`;
};

// 呼び出し
console.log(greet('花子')); // こんにちは、花子さん!

特徴:

  • 変数に代入される
  • ホイスティングされない
  • 無名関数(匿名関数)としても使用可能

3. アロー関数(Arrow Function)ES6

// アロー関数
const greet = (name) => {
  return `こんにちは、${name}さん!`;
};

// 簡潔な構文(単一式の場合)
const square = x => x * x;

特徴:

  • thisのバインドが異なる(後述)
  • 簡潔な構文
  • コンストラクタとして使用できない

関数の呼び出し

基本的な呼び出し

function add(a, b) {
  return a + b;
}

const result = add(3, 5); // 8

メソッドとしての呼び出し

const calculator = {
  add: function(a, b) {
    return a + b;
  }
};

calculator.add(2, 3); // 5

コンストラクタとしての呼び出し

function Person(name) {
  this.name = name;
}

const person = new Person('太郎');

間接的な呼び出し

function greet() {
  console.log(`こんにちは、${this.name}さん!`);
}

const user = { name: '花子' };
greet.call(user); // こんにちは、花子さん!

パラメータと引数

デフォルトパラメータ(ES6)

function greet(name = 'ゲスト') {
  console.log(`こんにちは、${name}さん!`);
}

greet(); // こんにちは、ゲストさん!
greet('太郎'); // こんにちは、太郎さん!

残余パラメータ(Rest Parameters)

function sum(...numbers) {
  return numbers.reduce((acc, num) => acc + num, 0);
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

引数の分割代入

function printUser({ name, age }) {
  console.log(`${name}さんは${age}歳です`);
}

const user = { name: '太郎', age: 25 };
printUser(user); // 太郎さんは25歳です

戻り値とスコープ

戻り値の重要性

// 戻り値がある関数
function add(a, b) {
  return a + b;
}

// 戻り値がない関数(undefinedを返す)
function noReturn() {
  // 何も返さない
}

スコープの挙動

let globalVar = 'グローバル';

function testScope() {
  let localVar = 'ローカル';
  console.log(globalVar); // アクセス可能
  console.log(localVar); // アクセス可能
}

testScope();
console.log(globalVar); // アクセス可能
// console.log(localVar); // エラー(アクセス不可)

高階関数とコールバック

高階関数の例

// 関数を引数に取る関数
function operate(a, b, operation) {
  return operation(a, b);
}

function add(x, y) { return x + y; }
function multiply(x, y) { return x * y; }

console.log(operate(3, 4, add)); // 7
console.log(operate(3, 4, multiply)); // 12

コールバック関数

// コールバックを使用した非同期処理の模倣
function fetchData(callback) {
  setTimeout(() => {
    callback('データ');
  }, 1000);
}

fetchData(data => {
  console.log('取得したデータ:', data);
});

即時実行関数式(IIFE)

// 定義と同時に実行
(function() {
  console.log('即時実行');
})();

// スコープを隔離する用途で使用
(function() {
  let privateVar = '外部からアクセス不可';
  // ここでの変数は外部から見えない
})();

再帰関数

// 階乗を計算する再帰関数
function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

console.log(factorial(5)); // 120

関数のベストプラクティス

  1. 単一責任の原則: 1つの関数は1つのことだけを行う
  2. 説明的な名前: 関数名から機能がわかるように
  3. 適切な長さ: 一般的に20行以内が目安
  4. 副作用を最小限に: 外部状態を変更しない純粋関数を目指す
  5. デフォルト引数を使用: 未定義エラーを防ぐ
  6. 適切なコメント: 複雑なロジックには説明を追加

演習問題

初級問題(3問)

  1. 次の関数を関数式とアロー関数で書き換えなさい。
   function multiply(a, b) {
     return a * b;
   }
  1. 次の関数呼び出しの出力結果は?
   function sayHello(name = 'ゲスト') {
     console.log(`こんにちは、${name}さん!`);
   }

   sayHello();
   sayHello('太郎');
  1. 次の関数の間違いを指摘し、修正しなさい。
   function sum(a, b) {
     console.log(a + b);
   }

   const result = sum(2, 3);
   console.log(result);

中級問題(6問)

  1. 次の関数を残余パラメータを使用して書き換えなさい。
   function sum(a, b, c) {
     return a + b + c;
   }
  1. 次のコードの出力結果とその理由を説明しなさい。
   let x = 10;

   function updateX() {
     x = 20;
   }

   updateX();
   console.log(x);
  1. 引数として受け取った数値が 正の数か負の数かを判定する関数 isPositive を作成しなさい。ゼロの場合は "zero"、正の数は "positive"、負の数は "negative" を返すようにしてください。
  2. 次の高階関数を使用して、配列の各要素を2倍にするコードを書きなさい。
   function mapArray(arr, transform) {
     const result = [];
     for (let item of arr) {
       result.push(transform(item));
     }
     return result;
   }
  1. オブジェクトを受け取り、そのプロパティをすべて表示する関数printObjectを作成しなさい。
  2. 次の再帰関数の動作を説明し、factorial(4)の呼び出し時の実行フローを追跡しなさい。
   function factorial(n) {
     if (n <= 1) return 1;
     return n * factorial(n - 1);
   }

上級問題(3問)

  1. カリー化された関数addを作成しなさい。以下のように動作するものとします。
console.log(add(2)(3)(4)()); // 9 
console.log(add(1)(2)()); // 3
  1. メモ化を実装したフィボナッチ関数を作成しなさい。再帰計算の効率を向上させること。
  1. 以下の要件を満たす関数createCounterを実装しなさい。

  • 呼び出すたびに1ずつ増加するカウンタを返す
  • 複数の独立したカウンタを作成可能
  • 初期値と増分値を指定可能

const counter1 = createCounter(); 
console.log(counter1()); // 1 
console.log(counter1()); // 2 
const counter2 = createCounter(10, 5); 
console.log(counter2()); // 15 
console.log(counter2()); // 20

解答例

初級問題解答

  1. 関数式とアロー関数で書き換え
   // 関数式
   const multiply = function(a, b) {
     return a * b;
   };

   // アロー関数
   const multiply = (a, b) => a * b;
  1. 呼び出しの出力結果
   こんにちは、ゲストさん!
   こんにちは、太郎さん!
    function sayHello(name = 'ゲスト') {
        console.log(`こんにちは、${name}さん!`);
    }

    sayHello();         // こんにちは、ゲストさん!
    sayHello('太郎');    // こんにちは、太郎さん!
  1. 間違いと修正
   // 間違い: console.logで出力しているだけで値を返していない
   // 修正例:
   function sum(a, b) {
     return a + b;
   }

中級問題解答

  1. 残余パラメータを使用して書き換え
   function sum(...numbers) {
     return numbers.reduce((acc, num) => acc + num, 0);
   }
  1. コードの出力結果とその理由
   20
    let x = 10;

    function updateX() {
        x = 20;     // 宣言してないのでグローバル変数として認識される
    }

    updateX();      // 関数内でグローバル変数xを更新しているため
    console.log(x);
  1. 正の数か負の数かを判定する関数 isPositive
    function isPositive(num) {
    if (num > 0) {
        return "positive";
    } else if (num < 0) {
        return "negative";
    } else {
        return "zero";
    }
    }
  1. 高階関数を使用して、配列の各要素を2倍にするコード
   const doubled = mapArray([1, 2, 3], x => x * 2);
   // [2, 4, 6]
  1. プロパティをすべて表示する関数printObject
   function printObject(obj) {
     for (const key in obj) {
       console.log(`${key}: ${obj[key]}`);
     }
   }
  1. factorial(4)の実行フロー
   factorial(4)
   → 4 * factorial(3)
     → 3 * factorial(2)
       → 2 * factorial(1)
         → 1 (ベースケース)
       → 2 * 1 = 2
     → 3 * 2 = 6
   → 4 * 6 = 24

上級問題解答

  1. カリー化された関数add
function add(a) { 
    return function(b) { 
         return function(c) { 
             return function() { 
                 return a + b + c;
             };
         };
    };
}

関数のカリー化とは、一回性で必要な引数を入れるのではなく、あらかじめ関数の引数を数段階に分けて、段階別に引数を自由に操作できる構造です。

  1. メモ化を実装したフィボナッチ関数
function fibonacci(n, memo = {}) { 
    if (n in memo) return 
    memo[n]; 
    if (n <= 1) return n; 
    memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo); 
    return memo[n]; 
}
  1. createCounter関数:
javascript function createCounter(initial = 0, step = 1) {
    let count = initial;
    return function() {
        count += step;
        return count;
    };
}