関数宣言・関数式・アロー関数式の違いについて

# 編集履歴
- 2025/03/12 以下のように内容修正
    - アロー関数式 vs 一般関数の時点でなく、関数宣言・関数式・アロー関数式として分けて書き直す
    - テーマの変更に合わせ、let/constの上書きなど、テーマと関連性が低い部分については略す
- 2025/03/07 記事作成

# 背景
オンライン診療サービスのWEBアプリケーションを開発するプロジェクトで働いている際に、
PRコードレビューを先輩エンジニアさんに投げたら
このような指摘がありました。

>functionとして定義された関数は
const func = () =>のアロー関数式のように統一していただきたいです！
そちらの方がより良いコードの書き方が身につけるはずです。

当時には実務経験があまりなかったため、
関数を使う時にはほぼ関数宣言（Function Declaration）として使っていました。

その後、レビュー対応のためにアロー関数式でメソッドを書き直して提出し、
関数の使い方として「関数宣言（Function Declaration）」だけではなく「関数式（Function Expression）」があることも学べましたが、

今回に関数宣言・関数式についてより理解を深めたく本記事を書くようになりました。

# 関数（メソッド）の書き方
## 関数宣言(Function Declaration)
一般的に使われる関数・メソッド文法です。

```javascript
function newFunc () {
 console.log("test")
}
```

コードのフローの内部にて、このように「特定の関数を宣言する」という意味を持って
「関数宣言（Function Declaration）」になります。

## 関数式(Function Expression)
functionから始まるのではなく、式(Expression)の形で関数が定義されるときに「関数式」と呼びます。
この時にはfunctionの後ろに関数名を指定しなくても、指定した変数名として関数が使えます。

```javascript
const newFunc = function (params) {
 console.log("test", params)
}
```

関数式を書く時には、function ()の関数名は省略ができます。
ただ、MDNのドキュメントの引用通りでは、
関数名をつけることでデバッグや再帰関数も容易に使えるそうです。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions

>However, a name can be provided with a function expression. Providing a name allows the function to refer to itself, and also makes it easier to identify the function in a debugger's stack traces:

もちろん、関数式の関数を呼び出す時には
関数が入っている変数名を使って呼び出すようになっています。

```javascript
const factorial = function fac(n) {
  return n < 2 ? 1 : n * fac(n - 1)
}

console.log(factorial(5)) //120
console.log(fac(5)) //Uncaught ReferenceError: fac is not defined
```

### アロー関数式
ES6から紹介された文法として、
関数式をより簡潔に作成できる文法です。

```javascript
const newFunc = (params) => {
 console.log("test", params)
}
```
アロー関数は「関数式」に含まれるため、基本的には動きも関数式と似ていますが
細かいところで違いがあります。

#### 場合によってより簡潔にも書ける 
関数式内部のロジックが`return 値`のみであれば、
`{ return foo }`のようなロジックを `() => ( foo )`の一行目で収めることができます。

```js
const arrowFunc = (a,b) => (a+b)
```
```js
const arrowFunc = (a,b) => {
  return a+b
}

//簡潔バージョン
const SimpleArrowFunc = (a,b) => a+b
```

{}(curly brace)を()に変えてreturn値のみ書けばOKです。
数式系だったり、コードがより簡単であれば、（）さえ省略できます。

ただ、目をおきまして、
Arrow Functionを使うときには
- {}のcurly braceではreturnを特に指定しなければ基本的にはvoid関数として扱われる（consoleで打てばundefined値を返していることがわかります）
- ()で紐付けばカッコの中にある値をreturnしますよーという機能があることを覚えておけばOK

などを覚えておきましたら問題なく使いこなせると思います。

このアロー関数はただのWEBサイトだけでなく、
WEBアプリケーションにもとても有用で、
Next.js・Reactなどのフレームワークのページコンポーネントに使われる.tsx(.jsx)形式のコンポーネントでも比較的に簡単なコードがかけます。

```typescript
const menuDisplay = (isSP?: boolean) => (
 <>
   <MenuIcon />
   <MenuItems isSP={isSP} />
 </>
)
```

# 共通点
呼び出されるとメソッド内部のロジックを実行します。
主に関数宣言の場合は関数名を呼び出し、
関数式は関数が入っている変数名を呼び出してロジックを実行します。

# 相違点
一貫性のあるロジックを紐づいてロジックを実行させるというところでは大きな違いはありませんが、
動きに細かい違いがあります。
代表的にまとめますと、Hoisting、this、argumentsです。strict modeではない時には、パラメータの宣言も含まれます。

## Hoisting

### 関数宣言
関数宣言の場合は、以下のようにHoistingができます。
一旦関数が宣言されていると、関数が定義される前にもアクセスができます。
```javascript
funcSample() // arrowFunc called

function funcSample () {
  console.log("arrowFunc called")
}
```

### 関数式・アロー関数式
関数式・アロー関数式両方Hoistingはできません。
関数式の場合は、関数式が定義されてから使えるようになります。

```javascript
arrowFuncSample() //Uncaught ReferenceError: arrowFuncSample is not defined

const arrowFuncSmample = () => {
  console.log("arrowFunc called")
}
```
アロー関数はもちろん、
var変数で定義された関数式もHoistingが使えません。
varの動き通りにfuncExpression変数を上段に上げましたが、まだundefinedとして認識していて、
関数式の宣言が行われていないため、関数のようには使えていない状態です。

```javascript
console.log(varVal) //undefined
funcExpressionSample() //Uncaught TypeError: funcExpressionSample is not a function

var varVal
var funcExpressionSample = function () {
  console.log("FuncExpression Called")
}
```

varをつけない場合にもReferenceErrorを出します。

```javascript
funcExpressionSample() //Uncaught ReferenceError: funcExpressionSample is not defined

funcExpressionSample = function () {
  console.log("FuncExpression Called")
}
```

## 関数名の表記
### 関数宣言
`function()`から始まり、関数名も必須になります。

```javascript
function (a,b) {
  console.log(a*b)
}

//Uncaught SyntaxError: Function statements require a function name
```

```javascript
function multiplyAB (a,b) {
  console.log(a*b)
}

multiplyAB(5,6) //30
```

### 関数式（function()、アロー関数式）
指定した変数名で該当の関数が呼び出されるため、省略できます。

```javascript
const plusValues = function (a,b) {
  console.log(a+b)
}

const plusValuesByArrowFunc = (a,b) => {
  console.log(a+b)
}

plusValues(3,4) //7
plusValuesByArrowFunc(3,4) //7
```

function関数式の場合は関数名がなくてもOKで、関数名を個別でつけることでもっとわかりやすく書いたり、内部で参照したりすることができます。
アロー関数の場合は特徴通りに関数名自体が持てません。（Anonymous Function）

```javascript
const plusThreeValues = plusThree (a,b,c) => a+b+c 
//Uncaught SyntaxError: Malformed arrow function parameter list
```

## 引数（パラメータ、arguments)について
### 同じパラメータの宣言
普通には発生しないことですが、
関数宣言・関数式・アロー関数式それぞれ違い動きを見せていました。

#### function構文（関数宣言・関数式）
```javascript
function duplicatedParams (a,b,a) {
  console.log(a+b)
}

duplicatedParams(1,2,3)// aが3に上書きされ, 3 + 2 = 5

const duplicatedParamsFunctionExpress = function(a,b,a) {
  console.log(a + b)
}

duplicatedParamsFunctionExpress(1,2,3)// aが3に上書きされ, 3 + 2 = 5
```
パラメータaが2回定義されている状態ですが、
後に定義されたaパラメータ（3番目のaパラメータ）が
1番目のaパラメータを上書きします。
function()を使っている時にはご覧のように同じパラメータとして定義しても
エラーを出さず、パラメータを上書きします。


#### アロー関数式
```javascript
const duplicatedParamArrowFunc = (a,b,a) => {
  console.log(a + b)
}

duplicatedParamArrowFunc(1,2,3) //Uncaught SyntaxError: Duplicate parameter name not allowed in this context
```
アロー関数式の場合には、
同じパラメータを連続で定義できません。


#### use strict
ただ、functionを使った関数だとしても、
use strictがついていればこのように同じパラメータを宣言することはできなくなります。

```javascript
"use strict"

function duplicatedParams (a,b,a) {
  console.log(a+b)
}

duplicatedParams(1,2,3)// Uncaught SyntaxError: Duplicate parameter name not allowed in this context

const duplicatedParamsFunctionExpress = function(a,b,a) {
  console.log(a + b)
}

duplicatedParamsFunctionExpress(1,2,3)// Uncaught SyntaxError: Duplicate parameter name not allowed in this context
```

### argumentsの使用
関数に渡された実引数の値を含めているargumentsについても、以下のような結果が得られました。
- function()系は使える
- アロー関数式は使えない

（argumentsに関してはMDN公式ドキュメントにも載っています。）
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments


#### Function（関数宣言・関数式）
```js
function showArgumentsByFunc (a,b) {
  console.log(a+b)
}

showArgumentsByFunc(3,3) //Arguments(2) [3, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]

const showArgumentsByFunctionExpression = function() {
  console.log(arguments)
}

showArgumentsByFunctionExpression(1,2,3) //Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
```
function()系は、argumentsがパラメータとして定義されていなくても類似配列としてアクセスできるようになっています。

#### アロー関数
argumentsがないため、アクセスできません。

```javascript
const showArgumentsByArrowFunction = () => {
  console.log(arguments)
}

showArgumentsByArrowFunction(1,4,7) 
//Uncaught ReferenceError: arguments is not defined at showArgumentsByArrowFunction
```
ご覧のように、アロー関数式はargumentsが定義されていないため、エラーが出るようになります。


### thisの使用
argumentsと同じです。
function()系は参照可能、アロー関数式は参照不可です。

#### Function（関数宣言・関数式）
```js
const normalObj = {
  name: "Ethel",
  cry: "OiiaOiia",
  test: function() { console.log(this.name + " cries " + this.cry) }
}
 
normalObj.test() //Ethel cries OiiaOiia
```

#### アロー関数
```js
const arrowObj = {
  name: "Ethel",
  cry: "OiiaOiia",
  test: ()=>console.log(this.name + " cries " + this.cry)
}

arrowObj.test() //  cries undefined
```

### その他
アロー関数はbinding値を持っていないため、
`this、arguments、super、 _prototype_` などが使えなく、
new Date()のようなConstructorとしても使えません。

よくは見えないコードですが、yieldも使えません。

function()使用の関数はthisを指定したら自分が呼ばれたオブジェクトを従いますが、
アロー関数式はbindingが指定されておらず、window・document・global object・globalthisなどを従います。

アロー関数式はConstructorとしても使えず、`_prototype_`もありません。

```js
const Foo = () => {}
const foo = new Foo() // TypeError: Foo is not a constructor
console.log("prototype" in Foo) // false
```

なので、以下の場合はアロー関数式を使うのは不利になり得ます。
- Constructorが必要
- this値を使う必要がある

## 相違点まとめ
| 分類 | 関数宣言 | 関数式 | アロー関数式 |
|:-----------|:-----------:|:------------:|:------------:|
| Hoisting  | 🟢        | ❌         | ❌         | 
| 関数名の必要の有無  | 必須       | 任意         | 無        | 
| this   | 🟢     | 🟢      | ❌       |
| arguments   | 🟢     | 🟢      | ❌         |
| 同一名のパラメータ定義       | 🟢[^us]        | 🟢[^us]         | ❌         |
| Constructor   | 🟢     | 🟢      | ❌         |


# 何を使うべきか
より安全なコードを書くなら関数式・アロー関数式に有利な面が一部あると思いますが、
その頃にコードレビューを受けた後・本記事を書き直した後考察をしたら以下のように思います。

- 詳しくは個人の好みやチーム開発のルールに従う
- ただし、この動き上の特徴を覚えておいて、理解度を持って実装することこそ大事

筆者の私は、予測できなかったHoistingとか上書きが防げる関数式、
特にアロー関数式を使ってロジックを書きます。

ただし、
関数式・アロー関数式の場合はHoistingができないということもあり、
特にアロー関数式にはthis・Constructorなどの機能が使えないことを覚えて書く必要があると思いました。


一旦、
AirBnBのJSオープンソースでは、Hoisting防止のためコードルールとして関数式を推奨していますが、

https://github.com/airbnb/javascript

「関数表現式の方が絶対的に有利」よりは「場合によって適切な関数の使い方があるはず」だと思います。

# まとめ
- 関数宣言（Function Declaration）・関数式（Function Expression）・アロー関数式(Arrow Function)三つとも、内部のロジックを回すという共通点はあるが、細かい違いがある
- その違いには、関数式や関数宣言一つの方が絶対的に有利・不利だということを示すわけではなく、場合によって適切な使い方があるということを示している
- そのため、チーム開発の場合PRのルールを従いつつ、関数の定義のやり方について理解・考察してコードを作成していくことこそ大事

# 参照
https://www.freecodecamp.org/news/the-difference-between-arrow-functions-and-normal-functions/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

https://www.w3schools.com/js/js_arrow_function.asp

https://github.com/airbnb/javascript?tab=readme-ov-file

https://github.com/airbnb/javascript/issues/794

[^us]:use strictの場合は❌