JavaScript: 通常の関数とアロー関数の違いは「書き方だけ」ではない。異なる性質が10個ほどある。

アロー関数は、`function`キーワード使った関数のシュガーシンタックス、つまり、書き方が違うだけで機能は同じだという説明を耳にしたことはありませんでしょうか？

この説明は、正確ではなく実際は様々な違いがあります。本稿では、これら2つの関数の性質の違いについて解説していきます。

:::note info

TypeScriptを交えた通常の関数とアロー関数の違いについての記事も書いてます。下記の記事もぜひご覧ください。

:::

https://typescriptbook.jp/reference/functions/function-expression-vs-arrow-functions


## 結論

本稿の結論を先に示すと、2つの関数の違いは次のとおりです。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889/0f503052-fcf1-36c8-2917-7feeb1318c60.png)

詳しく知りたい方は、続きをお読み頂ければと思います。

## 2種類の関数

JavaScriptには大きく分けて2つの関数があります。ひとつは、`function`キーワードを使って定義される関数です。

```javascript
function () {}
```

もうひとつは、アロー関数(arrow function)です。名前のとおり矢印記号`=>`を使って定義される関数です。

```javascript
() => {}
```

本稿では、アロー関数と`function`キーワードを使って定義される関数を区別するため、`function`キーワードを使うほうの関数を「通常関数」と呼ぶことにします。英文で見かけるregular functionの翻訳になりますが、これは公式の用語ではなく、解説の便宜上のものとご理解頂ければと思います。単に「関数」というときは、通常関数とアロー関数どちらも指すこととします。

![本稿での呼び分け](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889/6dfec9b7-6ddb-ed13-52b0-115d9db7821c.png)

## 関数の歴史

歴史的に見ると、通常関数は古くからある言語機能であるのに対し、アロー関数は新しいものです。アロー関数はES2015(ES6)で導入されました。導入にあたっては、関数を短く書きたい、thisを束縛したくないという2つの理由があります。


## 通常関数とアロー関数の性質の違い

通常関数とアロー関数では、構文が違うというのは見て分かると思います。構文についての違いはここでは解説しません。

ここでは、文法以外の相違点をひとつひとつ見ていくことにします。

### 違い1: thisの指すもの

通常関数には`this`があり、`this`が何を指すのかは、通常関数を実行したタイミングで決まります。

```javascript
function regular() {
    return this
}

// グローバルスコープで実行した場合、thisはグローバルオブジェクトを指す
console.log(regular() === window) //=> true

// メソッドとして実行した場合、thisはオブジェクトを指す
const obj = { method: regular }
console.log(obj.method() === obj) //=> true
```

通常関数の`this`の振る舞いの詳細についての解説は他の投稿に譲ります:

* [JavaScriptの「this」は「4種類」？？ - Qiita](https://qiita.com/takeharu/items/9935ce476a17d6258e27#%E3%81%BE%E3%81%A8%E3%82%81)
* [JavaScript の this を理解する多分一番分かりやすい説明 - Qiita](https://qiita.com/takkyun/items/c6e2f2cf25327299cf03)

アロー関数には、それ自体が保有する`this`はなく、関数の外の`this`を関数内で参照できるだけです。レキシカルスコープの`this`を参照します。つまり、アロー関数は定義したときに、`this`が指すものがひとつに決まり、どうやって関数が実行されるかに左右されなくなります。

```javascript
const arrow = () => {
    return this
}
// 関数の外側のthis
const lexicalThis = this

// 関数定義したタイミングで、関数の外側のthisを参照する
console.log(arrow() === lexicalThis) //=> true

// メソッドとして実行しても、thisはメソッドが属するオブジェクトを指さない
const obj = { method: arrow }
console.log(obj.method() === obj) //=> false
console.log(obj.method() === lexicalThis) //=> true

// ちなみに上のarrowのthisの振る舞いを、通常関数で再現すると次のようになります:
function regular() {
    return lexicalThis
}
```

以上の相違を踏まえると、同じ処理内容の関数でも、通常関数かアロー関数かによって、結果が異なってくる場合があるということが分かります:

```javascript
this.name = 'bar'

const foo = {
	name: 'foo',
	regular: function () {
		return this.name
	},
	arrow: () => {
		return this.name
	}
}

console.log(foo.regular()) //=> foo
console.log(foo.arrow()) //=> bar
```

### 違い2: newできるかどうか

通常関数は`new`することができますが、アロー関数は`new`することができません。つまり、アロー関数はコンストラクタになることができません。

```javascript
function regular() {
}
const arrow = () => {}

new regular()
new arrow() //=> TypeError: arrow is not a constructor
```

したがって、通常関数は`class`で`extends`できますが、アロー関数はできません:

```javascript
function regular() {}
const arrow = () => {}

class Foo extends regular {}
class Bar extends arrow {} //=> TypeError: Class extends value () => {} is not a constructor or null
```

### 違い3: `call`, `apply`, `bind`の振る舞い

通常関数は、`call`, `apply`, `bind`メソッドの第一引数で、その関数の`this`を指すオブジェクトを指定することができます。アロー関数は、指定しても無視されます。

```javascript
function regular() {
	return this
}
const arrow = () => {
	return this
}

const obj = {}

console.log(regular.bind(obj)() === obj) //=> true
console.log(arrow.bind(obj)() === obj) //=> false

console.log(regular.apply(obj) === obj) //=> true
console.log(arrow.apply(obj) === obj) //=> false

console.log(regular.call(obj) === obj) //=> true
console.log(arrow.call(obj) === obj) //=> false
```

### 違い4: `prototype`プロパティの有無

通常関数には`prototype`プロパティがありますが、アロー関数にはありません。

```javascript
function regular() {}
const arrow = () => {}

console.log(typeof regular.prototype) //=> object
console.log(typeof arrow.prototype) //=> undefined
```

ちなみに、どちらの関数も、内部プロパティ`[[Prototype]]`は一緒です:

```javascript
console.log(
    Object.getPrototypeOf(regular) 
        === Object.getPrototypeOf(arrow)
) //=> true
```

アロー関数の`call`メソッドなどは、この内部プロパティの`[[Prototype]]`のものになります:

```javascript
console.log(
    Object.getOwnPropertyNames(
        Object.getPrototypeOf(arrow)
    )
)
//=> [
//	'length',      'name',
//	'arguments',   'caller',
//	'constructor', 'apply',
//	'bind',        'call',
//	'toString'
//]
```

### 違い5: `arguments`の有無

通常関数は、`arguments`で引数リストを参照できますが、アロー関数では`arguments`が定義されていないため、引数リストを参照できません。アロー関数の`arguments`はレキシカルスコープ変数の`arguments`を参照するだけです。

```javascript
const arguments = 'hoge' // レキシカルスコープ変数

function regular() {
    console.log(arguments)
}
const arrow = () => {
    console.log(arguments)
}

regular(1, 2, 3) //=> [Arguments] { '0': 1, '1': 2, '2': 3 }
arrow(1, 2, 3) //=> hoge
```

ちなみに、アロー関数で引数リストを参照する場合は、可変長引数を定義する方法があります:

```javascript
const arrow = (...arguments) => {
    console.log(arguments)
}
arrow(1, 2, 3) //=> [ 1, 2, 3 ]
```

### 違い6: 引数名の重複

通常関数は、引数名の重複が許されますが、アロー関数は同じ引数名があるとエラーになります:

```javascript
function regular(x, x) {}
const arrow = (x, x) => {} // SyntaxError: Duplicate parameter name not allowed in this context
```

通常関数でもStrict Modeの場合は、引数名の重複がエラーになります:

```javascript
'use strict'
function regular(x, x) {} // SyntaxError: Duplicate parameter name not allowed in this context
```

### 違い7: 巻き上げ(hoisting)

通常関数は、`var`相当の巻き上げ(hoisting)が起こります。なので、関数定義前に呼び出しのコードを書いても、関数が実行できます:

```javascript
regular() // エラーにならない
function regular() {}
```

アロー関数は巻き上げが起こりません:

```javascript
arrow() // ReferenceError: Cannot access 'arrow' before initialization
const arrow = () => {}
```

これは、アロー関数の性質というよりかは、`const`の性質によるもので、通常関数の場合でも、`const`などを使った定義では、巻き上げが起こりません:

```javascript
regular() // ReferenceError: Cannot access 'regular' before initialization
const regular = function () {}
```

### 違い8: ジェネレータ関数

通常関数はジェネレータ関数を定義できますが、アロー関数はジェネレータ関数を定義する構文がそもそもありません。

```javascript
function* regular() {
    yield 1
}
```

### 違い9: 同じ関数名での再定義

通常関数は、同じ名前の関数を定義できます。最後の関数で上書きされます。

```javascript
function regular() {
    console.log(1)
}
function regular() {
    console.log(2)
}
regular() //=> 2
```

これも`var`相当の振る舞いと理解できます:

```javascript
var a = 1
var a = 2
console.log(a) //=> 2
```

アロー関数は`let`や`const`の仕様上、同じ関数名で定義を上書きすることができません:

```javascript
let arrow = () => {}
let arrow = () => {}
//=> SyntaxError: Identifier 'arrow' has already been declared
```

もちろん、アロー関数でも`var`で宣言すると、上書き可能です:

```javascript
var arrow = () => {}
var arrow = () => {}
```

### 違い10: `super`, `new.target`の有無

[アロー関数 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions)によれば、アロー関数には束縛された`super`や`new.target`が無いようですが、これを示すサンプルコードが思いつかなかったので割愛します。

## まとめ

本稿では、JavaScriptの2種類の関数、通常関数とアロー関数の性質の違いについて説明しました。通常関数とアロー関数では、様々な性質の違いあり、単純に書き方の違いではないことが分かりました。



---

最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします:relieved:→[Twitter@suin](https://twitter.com/suin)