JavaScriptにおける関数定義のイロハ

JavaScriptにおける関数定義のサンプルコードJS
この記事は約21分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●JavaScriptの関数とは

JavaScriptにおいて、関数は非常に重要な役割を果たします。

関数を使うことで、コードの再利用性が高まり、可読性も向上するのです。

では、具体的に関数とはどのようなものでしょうか?

関数は、ある特定のタスクを実行するために設計されたコードのブロックだと考えてください。

関数に名前をつけることで、そのタスクを実行したい時にはその名前を呼び出すだけでよくなります。

例えば、ユーザーの入力をチェックする処理を「validateInput」という関数にまとめたとします。

フォームの送信時には「validateInput」を呼び出すだけで、入力チェックの処理を実行できるようになるのです。

このように、関数を使うことで、同じ処理を何度も書く必要がなくなります。

コードがスッキリして読みやすくなりますし、修正もしやすくなるでしょう。

○関数の役割と特徴

関数の大きな役割は、コードの「モジュール化」と言えます。

プログラムを小さな部品(関数)に分割することで、それぞれの関数に独立した役割を持たせることができるのです。

また、関数にはいくつかの重要な特徴があります。

1つ目は、関数は「入力(引数)」を受け取り、「出力(戻り値)」を返すことができる点です。

これにより、関数は汎用性の高い「ブラックボックス」として機能します。

2つ目は、関数内で定義された変数は、その関数の中でしか参照できない(関数スコープ)という点です。

これによって変数の名前の衝突を避け、安全にコードを書くことができます。

○なぜ関数を使うのか

関数を使う大きなメリットは、コードの再利用性を高められることです。

同じような処理が複数の場所で必要になった際、関数化しておけばコードの重複を避けられます。

また、関数に適切な名前をつけることで、コードの可読性も格段に向上します。

関数名を見るだけで、その部分がどんな処理をしているのかが分かるようになるのです。

例えば、次のようなコードがあったとします。

// ユーザーの入力をチェックする
if (userName === "") {
  alert("名前を入力してください");
  return;
}
if (userEmail === "" || !userEmail.includes("@")) {
  alert("メールアドレスが正しくありません");
  return;
}

これを関数にまとめると、次のようになります。

function validateInput(name, email) {
  if (name === "") {
    alert("名前を入力してください");
    return false;
  }
  if (email === "" || !email.includes("@")) {
    alert("メールアドレスが正しくありません");
    return false;
  }
  return true;
}

// 関数の呼び出し
if (!validateInput(userName, userEmail)) {
  return;
}

このように書くことで、「validateInput」という関数名から「入力チェックをする関数だな」とすぐに分かりますし、その処理が必要になったら「validateInput」を呼び出すだけで済むようになります。

●関数の定義方法

JavaScriptで関数を定義する方法は、大きく分けて3つあります。

それぞれ使い分けることで、状況に応じた最適な関数の定義が可能になるのです。

○サンプルコード1:関数宣言

まずは、もっともシンプルな関数宣言から見ていきましょう。

関数宣言は、「function」キーワードを使って関数を定義する方法です。

// 関数宣言
function greet(name) {
  console.log("こんにちは、" + name + "さん!");
}

// 関数の呼び出し
greet("太郎"); // 出力結果:こんにちは、太郎さん!

ここでは、「greet」という名前の関数を定義しています。

この関数は「name」という引数を受け取り、”こんにちは、〜さん!”というメッセージをコンソールに出力します。

関数を呼び出す際は、関数名の後ろに括弧をつけ、その中に引数を渡します。

上記の例では「太郎」という文字列を引数として渡しているので、”こんにちは、太郎さん!”と出力されるわけですね。

○サンプルコード2:関数式

次に、関数式という定義方法を見てみましょう。

関数式は、関数を変数に代入する形で定義します。

// 関数式
const greet = function(name) {
  console.log("こんにちは、" + name + "さん!");
};

// 関数の呼び出し
greet("花子"); // 出力結果:こんにちは、花子さん!

ここでは、「greet」という変数に、関数を代入しています。

関数の内容は関数宣言の場合と同じですが、関数の定義の最後にセミコロン(;)が必要な点が異なります。

関数式を使うメリットは、関数を変数として扱えるところにあります。

つまり、関数を別の関数の引数として渡したり、関数を返り値として返したりできるようになるのです。

○サンプルコード3:アロー関数

最後に、ES6で導入されたアロー関数についても触れておきましょう。

アロー関数は、関数式をより簡潔に書くための記法です。

// アロー関数
const greet = (name) => {
  console.log("こんにちは、" + name + "さん!");
};

// 関数の呼び出し
greet("次郎"); // 出力結果:こんにちは、次郎さん!

アロー関数では、「function」キーワードの代わりに「=>」(アロー)を使います。

関数の引数は括弧の中に書き、関数の本体は中括弧({})の中に書きます。

アロー関数は、コードを短くスッキリと書けるというメリットがあります。

特に、関数の中身が1行で済む場合は、中括弧とreturn文を省略することもできるのです。

例えば、2つの数値を足し合わせる関数は、次のように書けます。

const add = (a, b) => a + b;

console.log(add(1, 2)); // 出力結果:3

関数の中身が1行なので、中括弧とreturn文を省略しています。

このように書くと、とてもコンパクトで読みやすいコードになりますね。

○定義方法の使い分け

ここまで、関数宣言、関数式、アロー関数の3つの定義方法を見てきました。

では、それぞれをどのように使い分ければよいのでしょうか?

基本的には、関数宣言か関数式のどちらかを使えば問題ありません。

ただし、関数を変数として扱いたい場合は関数式を使う必要があります。

アロー関数は、関数式の代替として使えます。

特に、コールバック関数など、その場で関数を定義して渡す場合に便利です。

例えば、配列の「map」メソッドを使って、各要素を2倍にする処理は次のように書けます。

const numbers = [1, 2, 3, 4, 5];

// アロー関数を使った例
const doubled = numbers.map(x => x * 2);

console.log(doubled); // 出力結果:[2, 4, 6, 8, 10]

「map」メソッドの引数にアロー関数を直接書くことで、コードがスッキリと読みやすくなっています。

●関数の引数

関数を定義する際、引数を使うことで関数の汎用性を高められることは前章で少し触れましたが、ここではもう少し掘り下げて見ていきたいと思います。

関数の引数は、関数に情報を渡すための重要な手段なのです。

○サンプルコード4:引数の受け取り方

まずは、引数の基本的な受け取り方から確認しましょう。

関数に引数を渡す際は、関数を呼び出す側で引数の値を指定します。

// 引数を受け取る関数の定義
function greet(name) {
  console.log("こんにちは、" + name + "さん!");
}

// 引数を渡して関数を呼び出す
greet("太郎");  // 出力結果:こんにちは、太郎さん!
greet("花子");  // 出力結果:こんにちは、花子さん!

ここでは、「greet」関数が「name」という引数を受け取るように定義されています。

関数を呼び出す際に、引数に値を渡すことで、その値を関数内で利用できるようになるわけですね。

上の例では、「太郎」や「花子」といった値を引数に渡しています。

関数内では、渡された引数の値を使ってメッセージを出力しているのがわかるでしょう。

○デフォルト引数

関数に引数が渡されなかった場合に備えて、デフォルト値を設定しておくこともできます。

これをデフォルト引数と呼びます。

// デフォルト引数を使う関数の定義
function greet(name = "ゲスト") {
  console.log("こんにちは、" + name + "さん!");
}

// 引数なしで関数を呼び出す
greet();  // 出力結果:こんにちは、ゲストさん!

// 引数を渡して関数を呼び出す
greet("太郎");  // 出力結果:こんにちは、太郎さん!

ここでは、「name」引数にデフォルト値として「ゲスト」を設定しています。

引数なしで関数を呼び出した場合、「name」には「ゲスト」が代入されるので、”こんにちは、ゲストさん!”と出力されます。

引数が渡された場合は、デフォルト値ではなく渡された値が使われるので、”こんにちは、太郎さん!”と出力されるわけですね。

デフォルト引数を使えば、引数が省略された場合の処理を簡潔に書くことができます。

○可変長引数

引数の数が予め決まっていない場合、可変長引数を使うと便利です。

可変長引数を使うと、任意の数の引数を受け取ることができるのです。

// 可変長引数を使う関数の定義
function sum(...numbers) {
  let result = 0;
  for (let number of numbers) {
    result += number;
  }
  return result;
}

// 関数の呼び出し
console.log(sum(1, 2, 3));  // 出力結果:6
console.log(sum(4, 5, 6, 7));  // 出力結果:22

ここでは、「sum」関数が「…numbers」という可変長引数を受け取るように定義されています。

関数を呼び出す際、引数の数は任意で指定できます。

関数内では、「numbers」引数が配列として扱われます。

上の例では、「for…of」ループを使って配列の各要素を合計しているのがわかりますね。

1回目の呼び出しでは、1, 2, 3の合計値である6が返され、2回目の呼び出しでは、4, 5, 6, 7の合計値である22が返されています。

可変長引数を使えば、柔軟に引数を受け取ることができるのです。

●関数内のthisの扱い

JavaScriptの関数を使いこなすには、「this」というキーワードの理解が欠かせません。

thisは、関数が呼び出された際のコンテキスト(環境)を表すのですが、時としてその挙動に戸惑うこともあるでしょう。

ここでは、そんなthisの扱い方について見ていきたいと思います。

○サンプルコード5:通常の関数とアロー関数の違い

まずは、通常の関数とアロー関数におけるthisの違いを見てみましょう。

次のコードをご覧ください。

const obj = {
  foo: function() {
    console.log(this);
  },
  bar: () => {
    console.log(this);
  }
};

obj.foo();  // 出力結果:obj
obj.bar();  // 出力結果:グローバルオブジェクト(ブラウザならwindow)

ここでは、「obj」というオブジェクトに「foo」と「bar」という2つのメソッドを定義しています。

「foo」は通常の関数、「bar」はアロー関数です。

「obj.foo()」を呼び出すと、「foo」関数内のthisは「obj」を指します。

一方、「obj.bar()」を呼び出すと、「bar」関数内のthisはグローバルオブジェクト(ブラウザ環境ならwindowオブジェクト)を指すのです。

なぜこのような違いが生じるのでしょうか?

実は、アロー関数は、定義された時点での外側のスコープのthisを引き継ぐという特徴があります。

つまり、アロー関数内のthisは、その関数が定義された場所のthisと同じになるのです。

一方、通常の関数は、呼び出し方によってthisの参照先が変わります。

メソッドとして呼び出された場合は、そのメソッドを所有するオブジェクトがthisになるというわけですね。

○メソッドにおけるthis

上の例でも見たように、メソッドとして呼び出された関数内のthisは、そのメソッドを所有するオブジェクトを指します。

これは、オブジェクト指向プログラミングにおいて非常に重要な概念です。

次の例を見てみましょう。

const person = {
  name: "太郎",
  greet: function() {
    console.log("こんにちは、" + this.name + "です。");
  }
};

person.greet();  // 出力結果:こんにちは、太郎です。

ここでは、「person」オブジェクトに「name」プロパティと「greet」メソッドを定義しています。

「greet」メソッド内では、「this.name」としてオブジェクトの「name」プロパティにアクセスしています。

「person.greet()」を呼び出すと、期待通り”こんにちは、太郎です。”と出力されますね。

メソッド内のthisが「person」オブジェクトを指しているからこそ、「this.name」で「person」の「name」プロパティにアクセスできるのです。

○コールバック関数におけるthis

コールバック関数におけるthisの扱いは、少しややこしいかもしれません。

次の例を見てみましょう。

const obj = {
  foo: function() {
    setTimeout(function() {
      console.log(this);
    }, 1000);
  }
};

obj.foo();  // 出力結果:グローバルオブジェクト(ブラウザならwindow)

ここでは、「obj」オブジェクトの「foo」メソッド内で、「setTimeout」関数を使ってコールバック関数を登録しています。

コールバック関数内では、thisを出力しています。

「obj.foo()」を呼び出すと、1秒後にコールバック関数が実行され、グローバルオブジェクトが出力されます。

期待していた「obj」ではないのです。

なぜこうなるのでしょうか?

実は、コールバック関数は、それを受け取った関数(この例ではsetTimeout)によって呼び出されます。

そのため、コールバック関数内のthisは、グローバルオブジェクトを指してしまうのです。

このような問題を避けるには、アロー関数を使うのが一つの解決策です。

先ほど見たように、アロー関数は定義時のthisを引き継ぐので、次のように書けば期待通りの動作になります。

const obj = {
  foo: function() {
    setTimeout(() => {
      console.log(this);
    }, 1000);
  }
};

obj.foo();  // 出力結果:obj

アロー関数を使うことで、コールバック関数内のthisが「obj」を指すようになりましたね。

●関数の応用

これまで、JavaScriptの関数の基本的な定義方法や引数、thisの扱いなどについて見てきました。

ここからは、もう一歩踏み込んで、関数のより応用的な使い方について探っていきましょう。

JavaScriptの関数は非常に柔軟で、様々な場面で活用できます。

ここでは、コールバック関数、高階関数、即時実行関数式(IIFE)といった、JavaScriptならではの関数の使い方を見ていきたいと思います。

○サンプルコード6:コールバック関数

まずは、コールバック関数から見ていきましょう。

コールバック関数とは、ある関数の引数として渡される関数のことを指します。

次の例を見てみましょう。

function calculate(a, b, operation) {
  return operation(a, b);
}

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

function multiply(a, b) {
  return a * b;
}

console.log(calculate(2, 3, add));  // 出力結果:5
console.log(calculate(2, 3, multiply));  // 出力結果:6

ここでは、「calculate」関数が「operation」という関数を引数として受け取っています。

「calculate」関数を呼び出す際、「add」関数や「multiply」関数を引数として渡すことで、それぞれの処理を実行しているのがわかりますね。

このように、関数を引数として渡すことで、その場その場に応じた処理を柔軟に行うことができるのです。

これがコールバック関数の強みです。

○サンプルコード7:高階関数

次に、高階関数について見ていきましょう。

高階関数とは、関数を引数として受け取ったり、関数を返り値として返したりする関数のことを指します。

先ほどの「calculate」関数も、高階関数の一種と言えます。

もう一つ例を見てみましょう。

function multiplyBy(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = multiplyBy(2);
const triple = multiplyBy(3);

console.log(double(5));  // 出力結果:10
console.log(triple(5));  // 出力結果:15

ここでは、「multiplyBy」関数が関数を返しています。

その関数は、「factor」という値を覚えていて、それに応じた計算を行うのです。

「multiplyBy(2)」を呼び出すと、「factor」が2の関数が返されます。

その関数を「double」という変数に代入しています。

同様に、「multiplyBy(3)」を呼び出して得られた関数を「triple」に代入しています。

「double(5)」を呼び出すと、5が2倍された10が返されます。

「triple(5)」を呼び出すと、5が3倍された15が返されるわけですね。

このように、関数を返り値として返すことで、その時々に応じた処理を生成することができます。

これが高階関数の強力な点なのです。

○即時実行関数式(IIFE)

続いて、即時実行関数式(IIFE)について見ていきましょう。

IIFEは、定義と同時に実行される関数のことを指します。

次のように書きます。

(function() {
  console.log("こんにちは");
})();

// 出力結果:こんにちは

ここでは、関数を丸括弧で囲み、その直後に別の丸括弧を置くことで、関数を定義すると同時に実行しています。

IIFEを使うメリットは、関数内で定義した変数を外部から隠蔽できる点にあります。

IIFEの中で定義された変数は、その関数の中でしか参照できません。

これにより、グローバルスコープの汚染を防ぐことができるのです。

○ジェネレーター関数

最後に、ジェネレーター関数についても触れておきましょう。

ジェネレーター関数は、関数の実行を途中で中断し、再開できる特殊な関数です。

ジェネレーター関数は、「function*」というキーワードを使って定義します。

関数内では、「yield」キーワードを使って値を返します。

次の例を見てみましょう。

function* countUp() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = countUp();

console.log(generator.next().value);  // 出力結果:1
console.log(generator.next().value);  // 出力結果:2
console.log(generator.next().value);  // 出力結果:3

ここでは、「countUp」というジェネレーター関数を定義しています。

この関数は、「yield」キーワードを使って1、2、3という値を順番に返します。

「countUp()」を呼び出すと、ジェネレーターオブジェクトが返されます。

そのオブジェクトの「next()」メソッドを呼び出すたびに、「yield」までの処理が実行され、その値が返されるのです。

●よくあるエラーと対処法

JavaScriptの関数を使っていると、時々エラーに遭遇することがあります。

ここでは、よく見られるエラーとその対処法について見ていきましょう。

エラーにうまく対処できるようになることは、JavaScriptを使いこなす上で欠かせないスキルなのです。

○関数が定義されていない

「関数が定義されていない」というエラーは、まさにその名の通り、呼び出そうとした関数が定義されていない場合に発生します。

次のようなコードを見てみましょう。

hello();  // エラー:hello is not defined

function hello() {
  console.log("こんにちは");
}

ここでは、「hello」関数を定義する前に呼び出そうとしているため、エラーが発生しています。

この問題を解決するには、関数を呼び出す前に定義するようにしましょう。

上のコードを次のように修正します。

function hello() {
  console.log("こんにちは");
}

hello();  // 出力結果:こんにちは

関数を定義してから呼び出すようにすれば、エラーは発生しません。

ただ、関数式を使う場合は注意が必要です。

次のコードを見てください。

hello();  // エラー:hello is not defined

const hello = function() {
  console.log("こんにちは");
};

このように、関数式を使って関数を定義する場合、その定義より前で関数を呼び出すとエラーになります。

これは、関数式の場合、定義が実行されるまでは関数が存在しないからです。

○引数の数が合っていない

「引数の数が合っていない」というエラーは、関数に期待された数の引数が渡されなかった場合に発生します。

次の例を見てみましょう。

function greet(name) {
  console.log("こんにちは、" + name + "さん");
}

greet();  // 出力結果:こんにちは、undefinedさん

ここでは、「greet」関数は引数を1つ期待しているのに、引数なしで呼び出されています。

この場合、「name」の値は「undefined」になるので、”こんにちは、undefinedさん”と出力されるわけですね。

この問題を解決するには、関数を呼び出す際に適切な数の引数を渡すようにしましょう。

function greet(name) {
  console.log("こんにちは、" + name + "さん");
}

greet("太郎");  // 出力結果:こんにちは、太郎さん

引数の数を合わせることで、期待通りの動作になります。

もし引数が渡されなかった場合にデフォルトの値を使いたいのであれば、デフォルト引数を使うのも一つの方法です。

function greet(name = "ゲスト") {
  console.log("こんにちは、" + name + "さん");
}

greet();  // 出力結果:こんにちは、ゲストさん

デフォルト引数を使えば、引数が渡されなかった場合にデフォルトの値が使われるので、エラーを防ぐことができます。

○thisの参照先が期待と違う

「thisの参照先が期待と違う」というエラーは、関数内のthisが期待していたオブジェクトを指していない場合に発生します。

次の例を見てみましょう。

const obj = {
  name: "太郎",
  greet: function() {
    console.log("こんにちは、" + this.name + "です。");
  }
};

const greet = obj.greet;
greet();  // 出力結果:こんにちは、undefinedです。

ここでは、「obj」オブジェクトの「greet」メソッドを別の変数に代入し、その変数経由で関数を呼び出しています。

この場合、関数内のthisはグローバルオブジェクト(ブラウザならwindow)を指すので、「this.name」は「undefined」になるのです。

この問題を解決するには、関数内のthisが期待通りのオブジェクトを指すようにする必要があります。

一つの方法は、「bind」メソッドを使ってthisを束縛することです。

const obj = {
  name: "太郎",
  greet: function() {
    console.log("こんにちは、" + this.name + "です。");
  }
};

const greet = obj.greet.bind(obj);
greet();  // 出力結果:こんにちは、太郎です。

「bind」メソッドを使って「obj」をthisとして束縛することで、期待通りの動作になりました。

または、アロー関数を使うのも一つの解決策です。

アロー関数は、定義された場所のthisを引き継ぐので、次のようにすれば問題ありません。

const obj = {
  name: "太郎",
  greet: () => {
    console.log("こんにちは、" + this.name + "です。");
  }
};

const greet = obj.greet;
greet();  // 出力結果:こんにちは、太郎です。

アロー関数を使えば、thisの束縛を気にする必要がなくなるのです。

まとめ

JavaScriptの関数は、コードの再利用性と可読性を高める上で欠かせない機能です。

関数宣言、関数式、アロー関数の3種類の定義方法を使い分け、引数の扱いやthisの参照先に注意しながら活用することが大切です。

コールバック関数や高階関数、即時実行関数式、ジェネレーター関数など、関数のより応用的な使い方を身につけることで、JavaScriptの可能性がさらに広がるでしょう。

関数を使う際のエラーにも適切に対処できるようになることが、JavaScriptのスキルアップには欠かせません。

関数は、JavaScriptプログラミングの根幹をなす機能です。

基本をしっかりと身につけ、積極的に関数を活用していきましょう。