13. アロー関数
目次
この本のサポートをお願いします: 購入する (PDF、EPUB、MOBI) または 寄付する
(広告です。ブロックしないでください。)

13. アロー関数



13.1 概要

アロー関数には2つの利点があります。

まず、従来の関数式よりも簡潔です。

const arr = [1, 2, 3];
const squares = arr.map(x => x * x);

// Traditional function expression:
const squares = arr.map(function (x) { return x * x });

次に、`this` は周囲のスコープから取得されます(*レキシカル*)。そのため、`bind()` や `that = this` はもう必要ありません。

function UiComponent() {
    const button = document.getElementById('myButton');
    button.addEventListener('click', () => {
        console.log('CLICK');
        this.handleClick(); // lexical `this`
    });
}

以下の変数はすべてアロー関数内でレキシカルです。

13.2 従来の関数は、`this` のため、メソッド以外の関数としては不適切です

JavaScript では、従来の関数は以下のように使用できます。

  1. メソッド以外の関数
  2. メソッド
  3. コンストラクタ

これらの役割は衝突します。役割 2 と 3 のため、関数は常に独自の `this` を持ちます。しかし、そのため、コールバック(役割 1)内から周囲のメソッドの `this` にアクセスすることができません。

以下の ES5 コードで確認できます。

function Prefixer(prefix) {
    this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) { // (A)
    'use strict';
    return arr.map(function (x) { // (B)
        // Doesn’t work:
        return this.prefix + x; // (C)
    });
};

C 行では `this.prefix` にアクセスしたいのですが、B 行の関数の `this` が A 行のメソッドの `this` をシャドウしているため、アクセスできません。strict モードでは、メソッド以外の関数では `this` は `undefined` であるため、`Prefixer` を使用するとエラーが発生します。

> var pre = new Prefixer('Hi ');
> pre.prefixArray(['Joe', 'Alex'])
TypeError: Cannot read property 'prefix' of undefined

ECMAScript 5 では、この問題を回避する 3 つの方法があります。

13.2.1 解決策 1: `that = this`

`this` をシャドウされていない変数に代入できます。それが以下の A 行で行われていることです。

function Prefixer(prefix) {
    this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
    var that = this; // (A)
    return arr.map(function (x) {
        return that.prefix + x;
    });
};

これで `Prefixer` は期待通りに動作します。

> var pre = new Prefixer('Hi ');
> pre.prefixArray(['Joe', 'Alex'])
[ 'Hi Joe', 'Hi Alex' ]

13.2.2 解決策 2: `this` に値を指定する

いくつかの Array メソッドには、コールバックを呼び出す際に `this` が持つべき値を指定するための追加パラメータがあります。それが以下の A 行の最後のパラメータです。

function Prefixer(prefix) {
    this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
    return arr.map(function (x) {
        return this.prefix + x;
    }, this); // (A)
};

13.2.3 解決策 3: `bind(this)`

メソッド `bind()` を使用して、`this` が呼び出し方法(`call()`、関数呼び出し、メソッド呼び出しなど)によって決定される関数を、`this` が常に同じ固定値である関数に変換できます。それが以下の A 行で行われていることです。

function Prefixer(prefix) {
    this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
    return arr.map(function (x) {
        return this.prefix + x;
    }.bind(this)); // (A)
};

13.2.4 ECMAScript 6 の解決策: アロー関数

アロー関数は解決策 3 とよく似ています.しかし、それらを `this` をレキシカルにシャドウしない新しい種類の関数と考えるのが最善です。つまり、通常の関数とは異なります(より少ない処理を行うとも言えます)。通常の関数にバインディングを追加したものではありません。

アロー関数を使用すると、コードは次のようになります。

function Prefixer(prefix) {
    this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
    return arr.map((x) => {
        return this.prefix + x;
    });
};

コードを完全に ES6 化するには、クラスとよりコンパクトなアロー関数を使用します。

class Prefixer {
    constructor(prefix) {
        this.prefix = prefix;
    }
    prefixArray(arr) {
        return arr.map(x => this.prefix + x); // (A)
    }
}

A 行では、アロー関数の 2 つの部分を調整することで、数文字節約しています。

13.3 アロー関数の構文

「ファット」アロー `=>` (シンアロー `->` とは対照的に)は、ファットアロー関数が非常によく似ている CoffeeScript との互換性のために選択されました。

パラメータの指定

    () => { ... } // no parameter
     x => { ... } // one parameter, an identifier
(x, y) => { ... } // several parameters

本体の指定

x => { return x * x }  // block
x => x * x  // expression, equivalent to previous line

ステートメントブロックは通常の関数本体のように動作します。たとえば、値を返すには `return` が必要です。式本体では、式は常に暗黙的に返されます。

式本体を持つアロー関数がどれほど冗長性を減らせるかに注目してください。比較してみてください。

const squares = [1, 2, 3].map(function (x) { return x * x });
const squares = [1, 2, 3].map(x => x * x);

13.3.1 単一パラメータを囲む括弧の省略

パラメータを囲む括弧の省略は、パラメータが単一の識別子で構成されている場合にのみ可能です。

> [1,2,3].map(x => 2 * x)
[ 2, 4, 6 ]

それ以外の場合は、パラメータが 1 つだけでも括弧を入力する必要があります。たとえば、単一のパラメータを分割代入する場合は括弧が必要です。

> [[1,2], [3,4]].map(([a,b]) => a + b)
[ 3, 7 ]

また、単一のパラメータにデフォルト値がある場合は括弧が必要です(`undefined` はデフォルト値をトリガーします!)。

> [1, undefined, 3].map((x='yes') => x)
[ 1, 'yes', 3 ]

13.4 レキシカル変数

13.4.1 変数値の伝播: 静的 vs 動的

変数の値を伝播するには、次の 2 つの方法があります.

まず、静的に(レキシカルに): 変数にアクセスできる場所は、プログラムの構造によって決定されます。スコープ内で宣言された変数は、(シャドウされていない限り)その中にネストされたすべてのスコープでアクセスできます。例えば

const x = 123;

function foo(y) {
    return x; // value received statically
}

次に、動的に: 変数値は関数呼び出しを介して伝播できます。例えば

function bar(arg) {
    return arg; // value received dynamically
}

13.4.2 アロー関数でレキシカルな変数

`this` のソースは、アロー関数の重要な特徴です。

値がレキシカルに決定される変数の完全なリストは次のとおりです。

13.5 構文上の落とし穴

構文に関連するいくつかの詳細が、時々つまずくことがあります。

13.5.1 アロー関数は非常に緩くバインドされます

`=>` を演算子として見ると、優先順位が低く、緩くバインドされると言えます。つまり、他の演算子と競合する場合、通常は他の演算子が優先されます。

その理由は、式本体を「くっつけたまま」にするためです。

const f = x => (x % 2) === 0 ? x : 0;

言い換えれば、`=>` は `===` や `?` に対して負けてほしいのです。次のように解釈してほしいのです。

const f = x => ((x % 2) === 0 ? x : 0);

もし `=>` が両方に対して勝つと、次のようになります。

const f = (x => (x % 2)) === 0 ? x : 0;

もし `=>` が `===` に対して負け、`?` に対して勝つと、次のようになります。

const f = (x => ((x % 2) === 0)) ? x : 0;

結果として、アロー関数が他の演算子と競合する場合、しばしば括弧で囲む必要があります。例えば

console.log(typeof () => {}); // SyntaxError
console.log(typeof (() => {})); // OK

反対に、`typeof` は括弧で囲まずに式本体として使用できます。

const f = x => typeof x;

13.5.2 アロー関数のパラメータの後に改行を入れないでください

ES6 では、アロー関数のパラメータ定義とアローの間に改行を入れることは禁止されています。

const func1 = (x, y) // SyntaxError
=> {
    return x + y;
};
const func2 = (x, y) => // OK
{
    return x + y;
};
const func3 = (x, y) => { // OK
    return x + y;
};

const func4 = (x, y) // SyntaxError
=> x + y;
const func5 = (x, y) => // OK
x + y;

パラメータ定義の *内部* での改行は問題ありません。

const func6 = ( // OK
    x,
    y
) => {
    return x + y;
};

この制限の根拠は、将来の「ヘッドレス」アロー関数(パラメータがゼロのアロー関数を定義するときに括弧を省略できる)に関して選択肢を開いたままにするためです。

13.5.3 ステートメントを式本体として使用することはできません

13.5.3.1 式とステートメント

簡単な復習(詳しくは「Speaking JavaScript」を参照してください

式は値を生成します(値に評価されます)。例:

3 + 4
foo(7)
'abc'.length

ステートメントは何かを行います。例:

while (true) { ··· }
return 123;

ほとんどの式1 は、ステートメントの位置に記述するだけで、ステートメントとして使用できます。

function bar() {
    3 + 4;
    foo(7);
    'abc'.length;
}
13.5.3.2 アロー関数の本体

式がアロー関数の本体である場合、中括弧は必要ありません。

asyncFunc.then(x => console.log(x));

ただし、ステートメントは中括弧で囲む必要があります。

asyncFunc.catch(x => { throw x });

13.5.4 オブジェクトリテラルの返却

JavaScript の構文の一部は曖昧です。たとえば、次のコードを考えてみましょう。

{
    bar: 123
}

これは次のいずれかになります。

アロー関数の本体は式またはステートメントのいずれかになる可能性があるため、オブジェクトリテラルを式本体にしたい場合は括弧で囲む必要があります。

> const f1 = x => ({ bar: 123 });
> f1()
{ bar: 123 }

比較のために、これは本体がブロックであるアロー関数です.

> const f2 = x => { bar: 123 };
> f2()
undefined

13.6 即時実行アロー関数

即時実行関数式 (IIFE) を覚えていますか?それらは次のようになり、ECMAScript 5 でブロックスコープと値を返すブロックをシミュレートするために使用されます。

(function () { // open IIFE
    // inside IIFE
})(); // close IIFE

即時実行アロー関数 (IIAF) を使用すると、数文字節約できます。

(() => {
    return 123
})();

13.6.1 セミコロン

IIFEと同様に、IIAFもセミコロンで終結させる(または同等の対策を講じる)必要があります。これは、連続する2つのIIAFが関数呼び出し(最初のものが関数、2番目のものがパラメータ)として解釈されるのを避けるためです。

13.6.2 ブロックボディを持つアロー関数の括弧付け

IIAFがブロックボディを持つ場合でも、括弧で囲む必要があります。これは、(直接)関数呼び出しできないためです。この構文上の制約の理由は、ボディが式であるアロー関数との整合性です(次に説明します)。

結果として、括弧はアロー関数の周囲に配置する必要があります。対照的に、IIFEでは選択肢があります。式全体を括弧で囲むことができます。

(function () {
    ···
}());

または、関数式だけを括弧で囲むこともできます。

(function () {
    ···
})();

アロー関数の動作を考えると、後者の括弧付けの方法が今後推奨されます。

13.6.3 式ボディを持つアロー関数の括弧付け

アロー関数の直後に括弧を置くことで呼び出すことができない理由を理解するには、式ボディの動作を調べる必要があります。式ボディの後の括弧は、アロー関数全体の呼び出しではなく、式の一部である必要があります。これは、前のセクションで説明したように、アロー関数が緩やかにバインドされることに関係しています。

例を見てみましょう。

const value = () => foo();

これは次のように解釈されるべきです。

const value = () => (foo());

次のように解釈されるべきではありません。

const value = (() => foo)();

さらに詳しく: 呼び出し可能エンティティに関する章のセクションには、ES6でIIFEとIIAFを使用する方法に関する詳細情報があります。ネタバレ:ES6は多くの場合、より良い代替手段を提供するため、IIFEとIIAFはめったに必要ありません。

13.7 アロー関数とbind()の比較

ES6のアロー関数は、多くの場合、Function.prototype.bind()の強力な代替手段となります。

13.7.1 メソッドの抽出

抽出されたメソッドをコールバックとして機能させるには、固定されたthisを指定する必要があります。そうでない場合、関数として呼び出され(thisundefinedまたはグローバルオブジェクトになります)、例えば、

obj.on('anEvent', this.handleEvent.bind(this));

代替手段として、アロー関数を使用できます。

obj.on('anEvent', event => this.handleEvent(event));

13.7.2 パラメータによるthis

次のコードは、巧妙なトリックを示しています。一部のメソッドでは、追加パラメータを介してthisの値を指定できるため、コールバックにbind()は必要ありません。filter()はそのようなメソッドの1つです。

const as = new Set([1, 2, 3]);
const bs = new Set([3, 2, 4]);
const intersection = [...as].filter(bs.has, bs);
    // [2, 3]

ただし、このコードはアロー関数を使用すると理解しやすくなります。

const as = new Set([1, 2, 3]);
const bs = new Set([3, 2, 4]);
const intersection = [...as].filter(a => bs.has(a));
    // [2, 3]

13.7.3 部分評価

bind()を使用すると、部分評価を実行できます。既存の関数のパラメータを入力することで、新しい関数を作成できます。

function add(x, y) {
    return x + y;
}
const plus1 = add.bind(undefined, 1);

ここでも、アロー関数の方が理解しやすいと思います。

const plus1 = y => add(1, y);

13.8 アロー関数と通常の関数の比較

アロー関数は、通常の関数とは2つの点のみ異なります。

それ以外は、アロー関数と通常の関数に違いはありません。たとえば、typeofinstanceofは同じ結果を生成します。

> typeof (() => {})
'function'
> () => {} instanceof Function
true

> typeof function () {}
'function'
> function () {} instanceof Function
true

アロー関数と従来の関数をいつ使用するかについての詳細は、呼び出し可能エンティティに関する章を参照してください。

13.9 FAQ:アロー関数

13.9.1 ES6には「ファット」アロー関数(=>)があるのに、「シン」アロー関数(->)がないのはなぜですか?

ECMAScript 6には、レキシカルなthisを持つ関数、いわゆる*アロー関数*の構文があります。ただし、動的なthisを持つ関数の矢印構文はありません。この省略は意図的なものでした。メソッド定義は、シンアローのユースケースのほとんどをカバーしています。本当に動的なthisが必要な場合は、従来の関数式を使用できます。

次: 14. クラス以外の新しいOOP機能