【初学者向け】従来の関数宣言とアロー関数の違いについて解説!

JavaScript

はじめに

JavaScriptにおける関数の定義方法には複数の選択肢があり、微妙な挙動の違いが初学者の方にとって戸惑いの元となることがあります。その中でも、「アロー関数」というJavaScriptの特徴的な関数の定義方法は、他の言語を学んだ経験がある方でも少し戸惑うことがあるかもしれません。

この記事では、JavaScriptにおける基本的な関数宣言から学び、その発展としてアロー関数の定義方法と特徴について解説していきます。また、従来の関数宣言とアロー関数をどのように使い分けると良いかについて述べます。

この記事の対象者

  • JavaScript初学者の方
  • ウェブ制作初学者の方
  • 関数の作り方に迷いがある方

この記事で学べること

  • 関数の基本
  • 従来の関数宣言とアロー関数の違い
  • アロー関数の特徴とメリット
  • アロー関数が使えないケース

関数宣言の基本

プログラミングにおいて、ある一連の処理に名前を付けて再利用できるようにしたものを「関数」と言います。関数を利用することで、特定の処理をコード上のあらゆる場所で呼び出すことができ、コードの保守性や可読性の向上が図れます。ここでは、JavaScriptにおいての基本的な関数宣言について解説します。

関数の基本構文

関数の宣言には「関数名」、「引数」、「処理」、「戻り値」を定義することができます。

  • 関数名:関数を識別するための名前。
  • 引数:関数に渡すことができる値。
  • 処理:関数が実行する処理。
  • 戻り値:関数が返す値。

基本的な関数の定義ではfunctionキーワードを用い、このような関数の記述をJavaScriptでは「関数宣言」と言います。基本構文は以下です。

function 関数名(引数1, 引数2, 引数3) {
  // 処理
  return 戻り値; // 必要に応じて処理の結果などを返す
}

月並みですが簡単な例を挙げます。

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

const result = add(3, 5);
console.log(result);
// 結果:8

上記ではadd()という関数を定義しています。add()関数は引数で受け取った二つの値を加算し、その結果を戻り値として返します。戻り値は変数に代入することができ、add()関数の結果をresult変数に代入してコンソールに出力しています。

処理を関数としてまとめるメリット

処理を関数としてまとめることには以下のようなメリットがあります。

  • 再利用性:同じ処理を何度も行う必要がある場合、関数を使ってその処理を再利用できます。同じ機能をコード内で再利用することで、コードの冗長性を減少させることができます。
  • 保守性:ある処理がコード内で何度も使われている場合、その処理を変更する必要が生じたときに修正が困難になることがあります。一連の処理を関数として定義しておくことで、修正箇所を関数の処理部分に絞り込むことができ、コードの保守性が向上します。
  • 可読性:関数に適切な名前やコメントを付けることで、処理部分のコードまでを読まなくても関数の意図や内容を理解することができます。コードエディタによってはJSDocsという記法のコメントをサポートしており、JSDocsを使用することで関数の使い方などを具体的に明示することができます。

関数宣言の基本について解説しました。続いて、JavaScript特有の記法であるアロー関数についてみてみましょう。

アロー関数

functionキーワードを用いた通常の関数宣言に加えて、JavaScriptでは「アロー関数」と呼ばれる関数の書き方があります。アロー関数による関数の定義はコードをより簡潔に表現することができ、複雑な機能がないため直感的に理解しやすいコードになります。

基本構文

アロー関数の定義では、引数や処理、戻り値を要素として持つところはfunctionキーワードによる関数宣言と同じです。

関数宣言との構文上の違いとして、アロー関数による関数の定義はfunctionキーワードを用いず、「=>」を使用します。また、アロー関数には関数名を付けず、変数に関数を代入するところも特徴的です。このアロー関数を代入した変数名を使って関数宣言と同様にアロー関数を呼び出すことができるので、この変数の名前が関数名になるととらえると分かりやすいかと思います。ちなみに、変数に代入する関数をJavaScriptでは「関数式」と呼びます。

アロー関数の構文は以下です。

const 関数名 = (引数1, 引数2, 引数3) => {
  // 処理
  return 戻り値;
}

関数宣言の例で述べたadd関数をアロー関数で記述すると、以下のようになります。

const add = (a, b) => {
  return a + b;
}

const result = add(3, 5);
console.log(result);
// 結果:8

アロー関数のメリット

functionキーワードを用いた関数宣言と比べてのアロー関数のメリットについて見てみましょう。主に以下のようなメリットが考えられます。

  • コードが簡潔になる
  • 信頼性が高い
  • 複雑な機能がないためシンプルなコードになる

順番に詳しく解説します。

コードが簡潔になる

アロー関数では、処理が一行の場合に「{}」や「return」を省略することができます。先ほどのadd()関数を例にして挙げると、以下のように記述することも出来ます。

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

JavaScriptでは、関数に引数として渡す「コールバック関数」を記述することが多く、functionキーワードを用いた方法では、たとえ簡単な処理でも複数行で書く必要があります。

const array1 = [1, 2, 3];

// array1から2と3を取り出す処理
const array2 = array1.filter(function(n) {
  return n === 2 || n === 3;
});

console.log(array2);
// 結果:[2, 3]

上記のコードは、配列として定義したfilter()メソッドを使用し、array1変数から「2」と「3」を取り出した新たな配列をarray2変数に代入しています。。アロー関数では、上記の処理を一行で書くことができます。

const array1 = [1, 2, 3];

// array1から2と3を取り出す処理
const array2 = array1.filter((n) => n === 2 || n === 3);

console.log(array2);
// 結果:[2, 3]

信頼性が高い

functionでの関数宣言は関数名や引数名の重複を許容します。この仕様は、JavaScriptがもともと規模の大きい開発を行うために作られた言語ではなかったという背景があるためと考えられます。以下は同じ名前の引数名を使用していますが、このコードは通常ではエラーになりません。

function hello(name, name) {
  console.log('hello ' + name + '!');
}
hello('taro');
// 結果:hello undefined!

引数名が重複している場合は、最後に指定した引数の値が採用されます。上記では、hello()関数の呼び出し時に第二引数を指定していないため「undefined」と解釈されます。

JavaScriptには「strictモード」という機能があります。これにより、問題が起きる可能性が高い処理を実行した場合にエラーを発生させることができます。引数名の重複はstrictモードを有効にすることで制限できます。

'use strict'; // strictモードを有効にする

// 「重複した引数名は許可されません」と怒られます
function hello(name, name) {
  console.log('hello ' + name + '!');
}

アロー関数では、strictモードの有効・無効に関わらず重複した引数名は許可されません。

// 「重複した引数名は許可されません」と怒られます
const hello = (name, name) => {
  console.log('hello ' + name + '!');
}

関数名の重複についても見てみましょう。関数名が重複している場合、strictモードを有効にしても関数宣言ではエラーが発生しません。

'use strict';

function hello(name) {
  console.log('hello ' + name + '!');
}

function hello() {
  console.log('hello!');
}

hello('taro');
// 結果:hello!

関数名が重複している場合、後に記述されている関数の処理が実行されます。よって、上記の例ではhello()関数の呼び出し時に引数を渡しても意味がありません。

アロー関数で関数を定義する場合はletやconstを使用します。これらは変数名の重複を許可せず、アロー関数においても同じです。VSCodeなどの高機能エディタを使用していれば、実行前にエディタ上でエラーを示してくれる場合もあります。

const hello = (name) => {
  console.log('hello ' + name + '!');
}

// 「'hello'はすでに宣言されている」と怒られます
const hello = (name) => {
  console.log('hello!');
}

varを使用した場合はこの限りではありませんが、アロー関数ではletやconstを利用することでプログラムが予期せぬ挙動になった場合に関数名が重複している可能性を排除することができます。

複雑な機能がないためシンプルなコードになる

アロー関数は、functionキーワードを用いた関数宣言から複雑な機能を取り除いたものだと考えることができます。アロー関数では、主に以下のような機能が取り除かれています。

  • ホイスティングされない
  • argumentsが生成されない
  • コンストラクタになれない

順番に見ていきましょう。

ホイスティングされない

JavaScriptにはホイスティング(巻き上げ)という機能があります。ホイスティングはfunctionキーワードによる関数宣言や、varを用いて変数を定義した場合に発生します。JavaScriptは、ホイスティングによりコードの実行よりも先に関数宣言の内容を読み取ります。なので、コード上のあらゆる場所に関数を定義することができ、関数の宣言場所を変更しても基本的には動作します。

// 関数宣言より先に記述しても動作します。
hello();

function hello() {
  console.log('hello');
}

ホイスティングは便利ですが、柔軟に関数の宣言場所を変更できるという仕様は一定のルールを設けないと煩雑なコードになる可能性があり、コードの規模が大きい場合は保守性や可読性において問題が起きる可能性があります。

アロー関数はホイスティングが発生しません。なので、アロー関数で定義された関数を呼び出す処理は必ず関数の定義より後に記述する必要があります。

// 以下は実行できません。
hello();

const hello = () => {
  console.log('hello');
}

// 以下は実行できます。
hello();

記述場所に制限が掛かるということは一見不便に感じるかもしれません。しかし、この制限の上でどこに関数の定義を記述したら分かりやすくなるかを考えてコードを書くと、ある程度シンプルで整理されたコードになる効果があります。

argumentsが生成されない

関数の引数には複数の値を指定できますが、時には引数の数がその関数の呼び出し時によって変化するような処理を実装する場合が考えられます。このように、状況によって引数の数が変化するものは「可変長引数」と呼ばれます。

関数宣言では、argumentsという配列に似た特殊なオブジェクトが自動で生成され、argumentsオブジェクトにアクセスすることで関数呼び出し時の引数の値を取得することができます。argumentsオブジェクトを使用することで可変長引数を表現することができます。

function example() {
  console.log(arguments);
}
example('apple', 'orange', 'grape');
// 結果;Arguments(3) ['apple', 'orange', 'grape']

argumentsオブジェクトは配列に似ていますが実際は異なるものです。通常ではArrayオブジェクトが持つ便利なメソッドを利用できず、配列として扱うには工夫が必要になりコードが冗長になりがちです。

function getApple() {
  // 配列に変換
  const argsArray = Array.from(arguments);
  const result = argsArray.filter(function(arg) {
    return arg === 'apple';
  });
  return result;
}
console.log(getApple('apple', 'orange', 'grape'));
// 結果:['apple']

argumentsオブジェクトの使用はあまりスマートではないため、基本的にはレスト演算子(残余引数とも言います)というものを使います。レスト演算子は「…」で表現します。

function getApple(...args) { // 可変長引数
  const result = args.filter(function(arg) {
    return arg === 'apple';
  });
  return result;
}
console.log(getApple('apple', 'orange', 'grape'));
// 結果:['apple']

レスト演算子を使用した引数は配列として扱われるので、Array.from()で配列に変換する必要が無く、argumentsオブジェクトを使用する場合と比べてシンプルで読みやすいコードになります。

アロー関数では、argumentsオブジェクトが生成されません。よって、引数の数がその関数の呼び出し時によって変化するような処理を実装する場合は必然的にレスト演算子を使用することになります。上記の例をアロー関数で記述すると以下のようになります。

const getApple = (...args) => args.filter((arg) => arg === 'apple');

console.log(getApple('apple', 'orange', 'grape'));
// 結果:['apple']

アロー関数は「return」や「{}」を省略できるため、functionキーワードによる関数宣言と比べて記述量が減り、ネストも浅くて可読性が高くなります。

コンストラクタになれない

JavaScriptには「コンストラクタ関数」というオブジェクトを生成するための特別な関数を定義することができます。コンストラクタ関数を使用することで、同じ構造やメソッドを持つオブジェクトを複数作成でき、JavaScriptはコンストラクタ関数の仕組みによってオブジェクト指向プログラミングの概念を実現しています。以下はコンストラクタ関数の例です。

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

コンストラクタ関数を使用してオブジェクトを作成する場合、「new」演算子を使用します。また、「this」という特殊なオブジェクトを用いて、引数の値と生成されるオブジェクトのプロパティとの紐付けを行います。コンストラクタ関数で作られたオブジェクトを「インスタンス」と言います。

// インスタンスの作成
const person1 = new Person('taro', '20');
const person2 = new Person('saburo', '30');

console.log(person1);
// 結果:Person {name: 'taro', age: '20'}

console.log(person2);
// 結果:Person {name: 'saburo', age: '30'}

コンストラクタ関数は、他の関数と区別するために関数名の先頭の文字を大文字にすることが慣習です。しかし、これはあくまで慣習であり、小文字の場合にエラーが発生するような強制力はありません。また、コンストラクタ関数はnew演算子を使用せずに通常の関数として呼び出すことも出来ます。よって、ある関数を利用する場合にnew演算子を使用するものかどうかの判断が必要になることが考えられます。

アロー関数で定義された関数はコンストラクタ関数になれず、new演算子でインスタンスを作成することができません。これは、その関数がコンストラクタではないということを示し、誤った関数の使用を防ぐ効果があります。

JavaScriptは一昔前ではクラス構文が使用できませんでしたが、現在ではクラス構文が使用できます。クラス構文によりコンストラクタ関数とは異なる表現でオブジェクトのひな形を定義できるため、プロジェクトのルールとして「関数はアロー関数で定義する」というものがあったとしてもアロー関数がコンストラクタになれないという制限は問題になりません。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const person = new Person('Taro', '20');

console.log(person);
// 結果:Person {name: 'Taro', age: '20'}

クラス構文は関数ではないので、役割が明確になり誤用を防ぐ効果があります。継承や親クラスの呼び出しなど、オブジェクトが複雑な構造になる場合にクラス構文で記述すると可読性が良くなります。クラス構文については以下の記事で詳しく解説しています。

アロー関数はthisに注意

functionキーワードを用いた関数宣言とアロー関数の違いにおいて、thisの扱いが異なるという点があります。関数宣言では、呼び出すタイミングで動的にthisが指すオブジェクトが決定します。例として、オブジェクトリテラルで簡単なオブジェクトを定義してみます。

const person = {
  name: 'Taro',
  seyHello: function() {
    // このthisはpersonオブジェクトです
    console.log('Hello, ' + this.name);
  }
}
person.seyHello();
// 結果:Hello, Taro

上記の例ではpersonオブジェクトでseyHello()メソッドを定義し、その中でthisを使用しています。このthisはpersonオブジェクトを指しており、ドットシンタックスでseyHello()メソッドを呼び出すとseyHello()メソッド内でpersonオブジェクトのnameプロパティを参照出来ているのが分かります。

person.seyHello();
// 結果:Hello, Taro

このseyHello()メソッドをアロー関数で記述した場合、異なる挙動になります。アロー関数においてのthisは定義時に決定し、その参照元は外部スコープが指すthisになります。そのため、上記のコードのseyHello()メソッドをアロー関数に変更すると意図しない結果となります。

const person = {
  name: 'Taro',
  seyHello: () => {
    // このthisはwindowオブジェクトです
    console.log('Hello, ' + this.name);
  }
}
person.seyHello();
// 結果:Hello,

上記の例では、seyHello()メソッド内のthisはwindowオブジェクトを参照します。そのため、「this.name」はwindow.nameプロパティの値を指すことになります。window.nameプロパティはwindowオブジェクトがもともと持っているプロパティであり、デフォルトでは空の文字列が設定されているので意図した挙動にならないことが分かります。

意図した値が取得できないだけなら早期に修正できますが、アロー関数のthisについて理解していないとより発見が困難なバグを生み出す可能性があります。以下は簡単な例です。

const person = {
  seyHello: () => {
    this.name = 'Taro';
    console.log('Hello, ' + this.name);
  }
}
person.seyHello();
// 結果:Hello, Taro

seyHello()メソッドの結果こそ意図したものになりますが、実際はwindow.nameプロパティに「Taro」という文字列を代入していることになります。

windowオブジェクトのnameプロパティはブラウザのウィンドウに名前を付けて操作する目的で使用されることがありますが、上記のようなコードが紛れていると不安定な挙動となる可能性があり、明らかなバグの要因です。

上記のようにオブジェクトリテラルでメソッドを定義する場合、アロー関数で記述することが適切ではない場合があることに注意が必要です。アロー関数内のthisは多くの場合でwindowオブジェクトを指すため、モダンJavaScriptの記法にこだわるのであればclass構文でオブジェクトを定義するという選択肢もあります。

class Person {
  constructor(name) {
    this.name = name;
  }
  seyHello() {
    console.log('Hello, ' + this.name);
  }
}
const person = new Person('Taro');
person.seyHello();
// 結果:Hello, Taro

thisの扱いには注意が必要と述べましたが、参照元が静的に決まるということでthisが何を指しているのかを分かりやすくする効果もあります。状況によってthisの参照元が変化する関数宣言と比べ、基本的には挙動が予測しやすいものになります。

関数宣言とアロー関数どっちを使う?

functionキーワードを用いた関数宣言とアロー関数のどちらを使うかについて、単に開発を行うという意味ではどちらでも問題です。アロー関数はコードの記述が少なくなるという便利な側面もありますが、「変数宣言と並べられると関数であることを見落としやすい」というような問題も孕んでおり、どちらにもメリット・デメリットはあるため状況によって選択できると良いでしょう。

アロー関数はEcmaScript2015(ES6)で導入された機能です。歴史的な経緯によりJavaScriptは大規模な開発に適していないとされていましたが、ES6以降の策定や豊富なライブラリなどによって今では大規模な開発においても人気があります。

アロー関数はJavaScriptでの開発体験を良くする目的で導入されたと考えることができるので、自由にコーディングルールを設定できる個人開発であれば積極的にアロー関数を用いると良いと思います。

従来の関数宣言を選択するケースとして、ホイスティングの機能を有効的に使いたい場合や、{}を用いたリテラル表現のオブジェクトにメソッドを定義したい場合などが考えられます。また、functionキーワードを用いることで関数であることが明示的になるというメリットもあるので、関数宣言のほうが好まれる場合もあります。

最後に

この記事ではJavaScriptにおける基本的な関数宣言から学び、アロー関数の特徴について解説しました。どちらを選択するかは好みの分かれるところではありますが、チーム開発はもちろん、個人開発や学習でも一定のルールを決めてコーディングを行うことはとても重要です。選択の方法について述べましたが、これがベストプラクティスというわけではなく、あくまで個人的な見解です。

アロー関数はES6で導入された機能ですが、そもそもES6についてよくわからないという方もいらっしゃるかと思います。以下の記事では、JavaScriptの歴史について触れ、EcmaScriptやES6とは何か、ES6で導入された機能などについて解説しています。興味のある方は参考にしていただけると幸いです。

タイトルとURLをコピーしました