はじめに
JavaScriptには、関数を宣言する方法が複数あり、それぞれ書き方や特徴が異なるので、まとめておきます。
関数の宣言方法は3種類
- 関数宣言
もっとも基本的な関数を宣言する方法👇
function fn(args){
// 処理
}
- 関数式
関数式はfunction式を使って関数を作る。また、関数名を省略できる👇
MDN: 関数式とは
const fn = function(){
// 処理
}
- アロー関数
アロー関数はES6から導入され、従来の関数宣言よりも短くかけることや、細かい挙動が異なる関数宣言方法👇
const fn = () => {
// 処理
}
// 引数がひとつの時には、引数のカッコが省略できる
const fn2 = arg => {
// 処理
}
// 引数が0個または2つ以上あるとカッコは省略できない
const fn3 = () => {
// 処理
}
const fn4 = (a, b) => {
// 処理
}
// さらに処理が1行の場合には、{ } と return も省略できる
// この書き方を簡潔文体ともいう
const fn5 = arg => arg.value;
従来の関数宣言とアロー関数の違い
-
短く書くことができる
こちらは先ほど説明した通り。function
の宣言やreturn
や()
{}
などが省略できる。 -
コンストラクター関数として使えない
従来の関数は、オブジェクトを生成するための機能が備わっている。これをコンストラクターと呼び、new
演算子を用いて、コンストラクタからインスタンスを作成することができる。
しかしアロー関数は、このコンストラクタの機能を持っていないため、誤って通常の関数をコンストラクターとして呼ぶことがない。
// 従来の関数宣言は、new演算子を用いてコンストラクターからインスタンスを生成できる
function Animal(name) {
this.name = name;
}
const dog = new Animal("シュナウザー");
console.log(dog.name) // シュナウザー
// アロー関数は、new演算子を用いることができない
const arrow = () => {}
new arrow() // Uncaught TypeError: arrow is not a constructor
-
this
従来の関数は、呼び出し方によってthisの指すものが変わります。
しかし、アロー関数は定義された時点でthisが固定化されます。
// 従来の関数は、呼び出し型によってthisの指すものが変わる
// 関数としてthisが呼ばれると、thisはwindowオブジェクトを指す
// メソッドとしてthisが呼ばれると、thisは呼び出し元のオブジェクトを指す
function fn() {
console.log(this);
}
fn() // this は window を指す
const fn2 = {}
fn2.outputLog = fn;
fn2.outputLog() ; // thisはfn2を指す
//-------------------------------------
// メソッドとして呼び出した場合も、arrow関数を定義した時点でthisが固定されているためwindowを指す。
const arrow = () => {
console.log(this);
}
console.log(arrow()); // window
const fn3 = {}
fn3.outputLog = arrow;
fn3.outputLog(); // window
-
arguments変数の有無
従来の関数宣言では、arguments
という特殊な変数が自動的に定義されています。argumentsは配列風のオブジェクトとして関数実行時に渡した値を格納しています。
function fn() {
console.log(arguments);
}
fn(1,2,3) // [1,2,3];
しかし、アロー関数はそもそもarguments
を持っていません。
-
引数名の重複
従来の関数宣言では、引数名が重複していてもエラーはでません。最後に宣言された引数名が採用されます。
function fn(a,a,a) {
console.log(a);
}
fn(1,1,1) // 1のみ出力
しかし、アロー関数では重複した引数名を書くと、シンタックスエラーを出力します。
const fn = (a,a,a) => {}
// Uncaught SyntaxError: Duplicate parameter name not allowed in this context
-
関数名の重複
従来の関数宣言では、同じ関数名で関数を定義すると最後に定義されたものが採用されます。
function fn () { console.log("fn1") };
function fn () { console.log("fn2") };
function fn () { console.log("fn3") };
fn() // "fn3"
アロー関数では、重複した関数名で関数を定義するとシンタックスエラーを出力します。
const fn = () => console.log("fn1")
const fn = () => console.log("fn2")
// Uncaught SyntaxError: Identifier 'fn' has already been declared
-
関数の定義と呼び出しの順序(巻き上げ)
従来の関数宣言では、巻き上げが起こるため関数宣言より前に関数を実行してもエラーは出ません。
アロー関数と関数式は巻き上げは起こらないため、宣言より前に関数を実行するとエラーが出ます。
これはアロー関数や関数式の使用というよりも、let
やconst
の仕様といった方が正しいです。
fn1() // "fn1" 関数の巻き上げが起こるため、定義前でも呼び出せる
function fn1() { console.log("fn1") }
fn2() // Uncaught ReferenceError: fn2 is not defined
const fn2 = () => { console.log("fn2") }
どうやって使い分けるべきか
これはあまり決まりきったものはないそうですが、実装する上で可読性やthisの扱い方によって使い分けるのが良いそうです。
-
コールバック関数
として使うときには、アロー関数を使用した方が簡潔に記述でき、実装の意図が伝わりやすい -
addEventListener
を使用するときには、従来の関数宣言の方が、thisがイベントの対象を示すため使いやすい -
メソッド
として関数を作る場合には関数宣言の方が、thisを素直に使える - 関数名から処理が理解できる場合には、関数宣言の巻き上げを利用すると実装の手順が分かり易い
- 関数を書いていることを目立たせたい場合には、関数宣言の方が明確。アロー関数や関数式では、ぱっと見で通常の変数定義と見分けがつかないことがある。
まとめ(感想)
アロー関数は確かに便利で、関数宣言の無駄なところやゆるい部分をキツくしてくれるため、エラーを起こしにくい実装を行う際にはとても有用だと感じました。
逆に関数宣言は、可読性の部分ではアロー関数に優っている部分が多いと思った。
ただ、巻き上げられても結局は処理を見に行く必要があるとも思うので、基本的にはアロー関数で書いていこうかなと思っています。