初めに
JavaScriptでは、関数の宣言をするのにいくつか方法があります。
従来の関数宣言
function hoge(name){
console.log(name);
}
アロー関数
const hoge = (name) => {
console.log(name);
}
どちらも「関数を定義する」という点で同じことができるように見えます。
違いもよくわからないし、どちらを利用するか迷ってしまう場面も初学者にはあるかもしれません。
普段はどちらを利用するのがよいでしょうか?
まずは結論
アロー関数を採用すれば、基本的に困る場面は少ないはずです。
(ただし、オブジェクトのメソッドを定義する際などは、従来の関数宣言を使いましょう。)
実際には、関数宣言とアロー関数で機能に違いがあり、それによるメリット・デメリットがあります。
以下では、これらの機能を比較し、アロー関数を利用するメリットを考えていきます。
アロー演算子のメリット
機能が少ない
アロー関数は後発ですが、従来の関数宣言から機能を間引いて作られています。
機能が少ないことは一見デメリットに見えます。
しかし、普段のコーディングで考慮すべき項目を減らすという意味で「誤用を防ぐ」「考えるべきポイントを減らす」という点でメリットになります。
(非推奨の機能は無いに越したことはない、といった感じでしょうか。)
アロー関数は従来の関数宣言と比較し以下の機能を持ちません。
コンストラクタ
function Animal(name){
this.name = name;
}
const cat = new Animal("mike");
console.log(cat.name);
// "mike"
関数宣言はコンストラクタ(オブジェクトを作成する)の機能を持ちます。
しかし、関数宣言をコンストラクタとしても使えてしまうのは
「この関数宣言は関数なのかコンストラクタなのか・・・?」
という感じに、逆に混乱を招きます。
アロー関数はコンストラクタとして利用できないため、この誤用を防ぐことができます。
call, apply, bind
それぞれ、関数に対しthisを紐づけるためのメソッドです。
function speak(){
console.log(this.voice);
}
const human = {voice:"HELLO!"}
speak.bind(human)();
// "HELLO!"
speak.apply(human);
// "HELLO!"
speak.call(human);
// "HELLO!"
speak()
// undefined
関数宣言で定義された関数にはこれが利用できるのに対し、アロー関数では利用できません。(正確には利用できますが、意味を持ちません。)
アロー関数のthisは、宣言したときに決定するため、コロコロ変化しない、という点で明確です。
その他
他にもいろいろと従来の関数宣言にあってアロー関数にない機能がありますが、ここでは割愛。
- arguments
- yield
(これらの機能はライトに使っている場合はあまり出会わないと思われます。)
thisの値が固定される
従来の関数宣言ではthisの内容がその時の状況に応じてコロコロ変化します。
それに対し、アロー関数ではthisは「定義した際にthisが指すもの」に固定されます。
const test = {
name:"TAMA",
method1: function(){
return function(){console.log(this.name);}
},
method2:function(){
return ()=>{console.log(this.name);}
}
}
const func1 = test.method1()
// 従来の関数宣言で宣言された関数リテラル
const func2 = test.method2()
// アロー関数で宣言された関数リテラル
func1() // ""
// 従来の関数宣言では、呼び出し時の状況に応じてthisに入る値が変わる。
// 呼び出し時にはthisにwindowが入っているため、window.nameが参照された
func2() // TAMA
// アロー関数では、宣言時のthisが利用される。
// 宣言時にはthisにtestが入っているため、test.nameが参照された
thisが指すものが明確になりますね。
この性質は、特にコールバック関数などを宣言する際に重宝します。
コールバック関数を書く際は、特殊なことをしない限り、アロー関数を使うのが無難です。
記述が簡潔
従来の関数宣言と比較し、アロー関数は記法が簡潔です。
// 従来の関数宣言
const hoge = function(){console.log("HELLO");}
// アロー関数
const hoge = ()=>{console.log("HELLO");}
アロー関数のデメリット
ここまではアロー関数のメリットを見てきましたが、次はデメリットを見ていきましょう。
オブジェクトのメソッド宣言に向かない
thisが宣言時に固定されてしまう都合上、アロー関数はオブジェクトのメソッド宣言には向きません。
const test = {
name:"TAMA",
method1: function(){
console.log(this.name);
},
method2: ()=>{
console.log(this.name);
},
}
test.method1() // TAMA
// 従来の関数宣言では、呼び出し時の状況に応じてthisに入る値が変わる。
// 呼び出し時にはthisにtestが入っているため、test.nameが参照された
test.method2() // ""
// アロー関数では、宣言時のthisが利用される。
// 宣言時にはthisにwindowが入っているため、window.nameが参照された
オブジェクトのメソッドを宣言する場合は従来の関数宣言を使いましょう。
後から関数を宣言できない
例えば、先に関数の呼び出しを書いてから、後から関数宣言をしたい場面があるかもしれません
hoge(); // Hello
function hoge(){
console.log("Hello");
}
従来の関数宣言では、このように後から関数を宣言しても、その前で利用することができます。
(これを「巻き上げ」と呼びます。)
しかし、アロー関数ではこの機能を利用することができません。
hoge(); //エラー!
const hoge() => {console.log("Hello");}
というのも、アロー関数は「関数リテラル」であるためです。(変数などに代入して使う無名関数のこと。)
関数リテラルでは、巻き上げを行うことができません。
ちなみに、従来の関数宣言についても関数リテラルとして運用した場合、巻き上げは発生しません。
hoge(); // エラー!
const hoge = function (){console.log("Hello");}
一見、関数であることが分かりづらい
これは他の言語を触ってからJS, TSを触り始めた人にクリティカルに刺さるのですが、アロー関数は書き方が関数っぽくないです。
// 従来の関数宣言
const piyo = [1, 2, 3];
function hoge (){
console.log("HELLO");
}
const fuga = {foo: "1", bar:"2"};
// アロー関数
const piyo = [1, 2, 3];
const hoge = () => {console.log("HELLO")};
const fuga = {foo: "1", bar:"2"};
従来の関数宣言の方が、明確に関数であることが分かりそうです。
共同開発者のバックグランドによっては、あえて従来の関数宣言を利用したほうが親切な場合があるかもしれません。
まとめ
- 機能的な側面からみると、普段はアロー関数を使うのが無難
- 「オブジェクトメソッドの宣言をする場合」「関数巻き上げを有効に使いたい場合」等は従来の関数宣言を選択する理由になる
- とはいえ、いろいろ事情はあるので、チーム内で関数の定義の使い分けルールについて決めておくとよさそう
最後に
本記事の内容は「どちらを使うべきか?」に視点をおいて、かなり簡略化して書かれています。
詳しく違いを知りたい場合は、参考記事からMDNやサバイバルTypeScriptあたりを読むと良いでしょう。
以上、後輩から使い分けについて聞かれてふと困ってしまった、よわよわエンジニアがお送りしました。