アロー関数式

アロー関数式は、従来の関数式の簡潔な代替構文ですが、意味的な違いや意図的な使用上の制限もあります。

  • アロー関数自身には thisargumentssuper へのバインドがないので、メソッドとして使用することはできません。
  • アロー関数はコンストラクターとして使用することはできません。 new をつけて呼び出すと TypeError が発生します。 new.target キーワードにアクセスすることもできません。
  • アロー関数は本体内で yield を使用することができず、ジェネレーター関数として作成することもできません。

試してみましょう

構文

js
() =>引数 => 

(引数) => 

(引数1, 引数N) => 

() => {}

引数 => {}

(引数1, 引数N) => {}

引数内での残余引数デフォルト引数分割代入には対応していますが、常に括弧が必要になります。

js
(a, b, ...r) => 
(a = 400, b = 20, c) => 
([a, b] = [10, 20]) => 
({ a, b } = { a: 10, b: 20 }) =>

アロー関数は、 async にすることができます。この場合は式の前に async キーワードを付けます。

js
async 引数 =>async (引数1, 引数2, ...引数N) => {}

解説

伝統的な無名関数を、最も単純なアロー関数に段階的に分解してみましょう。それぞれの段階も有効なアロー関数です。

メモ: 従来の関数式とアロー関数は、構文以外にも異なる点があります。次のいくつかの節で、その動作の違いを詳しく紹介します。

js
// 従来の無名関数
(function (a) {
  return a + 100;
});

// 1. "function" という語を削除し、引数と本体の開始中括弧の間に矢印を配置する
(a) => {
  return a + 100;
};

// 2. 本体の中括弧を削除と "return" という語を削除 — return は既に含まれています。
(a) => a + 100;

// 3. 引数の括弧を削除
a => a + 100;

上の例では、引数を囲む括弧と関数本体を囲む中括弧の両方を省略することができます。ただし、省略できるのは特定の場合のみです。

括弧を省略できるのは、関数に単一の単純な引数がある場合だけです。複数の引数がある場合、引数がない場合、デフォルト引数、分割代入、残余引数がある場合は、引数リストを括弧で囲む必要があります。

js
// 従来の無名関数
(function (a, b) {
  return a + b + 100;
});

// アロー関数
(a, b) => a + b + 100;

const a = 4;
const b = 2;

// 従来の関数(引数なし)
(function () {
  return a + b + 100;
});

// アロー関数(引数なし)
() => a + b + 100;

中括弧を省略できるのは、関数が直接式を返す場合だけです。本体に追加の処理がある場合は中括弧が必要となり、 return キーワードも必要となります。アロー関数はいつ何を返すかを推測することはできません。

js
// 従来の関数
(function (a, b) {
  const chuck = 42;
  return a + b + chuck;
});

// アロー関数
(a, b) => {
  const chuck = 42;
  return a + b + chuck;
};

アロー関数は常に無名です。アロー関数自身を呼び出す必要がある場合は、代わりに名前付き関数式を使用 してください。アロー関数を変数に割り当てて、名前を持たせることもできます。

js
// 従来の関数
function bob(a) {
  return a + 100;
}

// アロー関数
const bob2 = (a) => a + 100;

関数の本体

アロー関数は、簡潔文体 (concise body) か、もしくはより一般的なブロック文体 (block body) のどちらかを使用することができます。

簡潔文体においては、単一の式しか記述できないので、その式が暗黙的に return される値となります。しかし、ブロック文体においては、明示的に return 文を使用する必要があります。

js
const func = (x) => x * x;
// 簡潔構文の場合、暗黙の "return" があります

const func2 = (x, y) => {
  return x + y;
};
// ブロック文体では、明示的な "return" が必要です

簡潔文体 (params) => { object: literal } を使ってオブジェクトリテラルを返そうとしても、期待通りに動作しないことに注意しましょう。

js
const func = () => { foo: 1 };
// 呼び出した func() は undefined を返す!

const func2 = () => { foo: function () {} };
// SyntaxError: function statement requires a name

const func3 = () => { foo() {} };
// SyntaxError: Unexpected token '{'

これは、 JavaScript がアロー関数を簡潔文体とみなすのは、アローに続くトークンが左中括弧でない場合のみであるため、中括弧 ({}) 内のコードは一連の文として解釈され、 foo はオブジェクトリテラルのキーではなく、ラベルとなります。

これを修正するには、オブジェクトリテラルを括弧で囲んでください。

js
const func = () => ({ foo: 1 });

メソッドとしては使用不可

アロー関数式は自分自身で this を持たないので、メソッドではない関数にのみ使用してください。メソッドとして使用しようとするとどうなるか見てみましょう。

js
"use strict";

const obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c() {
    console.log(this.i, this);
  },
};

obj.b(); // undefined, Window { /* … */ } (またはグローバルオブジェクト) と表示
obj.c(); // 10, Object { /* … */ } と表示

Object.defineProperty() を使った他の例です。

js
"use strict";

const obj = {
  a: 10,
};

Object.defineProperty(obj, "b", {
  get: () => {
    console.log(this.a, typeof this.a, this); // undefined 'undefined' Window { /* … */ } (またはグローバルオブジェクト)
    return this.a + 10; // グローバルオブジェクト 'Window' を表すので、 'this.a' は 'undefined' を返す
  },
});

クラスの本体は this コンテキストを持っているので、クラスフィールドのようなアロー関数はクラスの this コンテキストを閉じ、アロー関数の本体の中の this はインスタンス(または静的フィールドの場合はクラス自体)を正しく参照します。しかし、これは関数自身のバインディングではなく、クロージャであるため、 this の値が実行コンテキストによって変わることはありません。

js
class C {
  a = 1;
  autoBoundMethod = () => {
    console.log(this.a);
  };
}

const c = new C();
c.autoBoundMethod(); // 1
const { autoBoundMethod } = c;
autoBoundMethod(); // 1
// 通常のメソッドであれば、この場合は未定義になるはずです。

アロー関数のプロパティはよく「自動バインドメソッド」と言いますが、これは通常のメソッドと同等だからです。

js
class C {
  a = 1;
  constructor() {
    this.method = this.method.bind(this);
  }
  method() {
    console.log(this.a);
  }
}

メモ: クラスフィールドはインスタンスで定義され、プロトタイプでは定義されません。そのため、インスタンスを作成するたびに新しい関数参照が作成され、新しいクロージャが割り当てられます。

同様の理由で、call()apply()bind() の各メソッドは、アロー関数で呼び出されても有益ではありません。アロー関数は、アロー関数が定義されているスコープに基づいて this の値を定義しており、関数の呼び出し方によってこの値が変わることはないからです。

arguments のバインドがない

アロー関数は自身の arguments オブジェクトを持ちません。そのため、この例では、 arguments は囲っているスコープでの同名変数への参照にすぎません。

js
function foo(n) {
  const f = () => arguments[0] + n; // foo は arguments をバインドしている。 arguments[0] は n である
  return f();
}

foo(3); // 3 + 3 = 6

メモ: arguments という変数は厳格モードでは宣言できないので、上のコードは構文エラーになります。これにより、 arguments のスコープ効果がより理解しやすくなります。

多くの場合、残余引数arguments オブジェクトの代わりに使えます。

js
function foo(n) {
  const f = (...args) => args[0] + n;
  return f(10);
}

foo(1); // 11

コンストラクターとしては使用不可

アロー関数はコンストラクターとして使用することができず、 new を付けて呼び出されるとエラーが発生します。また、 prototype プロパティもありません。

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

ジェネレーターとしては使用不可

yield キーワードはアロー関数内で使用できません(内部で入れ子になった関数が許可されている場合を除く)。結果として、アロー関数はジェネレーターとして使用できません。

アローの前の改行

アロー関数では、括弧とアロー(矢印)の間に改行を入れることができません。

js
const func = (a, b, c)
  => 1;
// SyntaxError: Unexpected token '=>'

しかし、矢印の後に改行を入れたり、以下のように括弧や中括弧を使用して、コードがきれいで滑らかになるように修正することができます。また、引数同士の間にも改行を入れることができます。

js
const func = (a, b, c) =>
  1;

const func2 = (a, b, c) => (
  1
);

const func3 = (a, b, c) => {
  return 1;
};

const func4 = (
  a,
  b,
  c,
) => 1;

アロー関数の優先順位

アロー関数のアロー(矢印)は演算子ではありませんが、アロー関数には特別な解釈ルールがあり、通常の関数とは演算子の優先順位の扱いが異なります。

js
let callback;

callback = callback || () => {};
// SyntaxError: invalid arrow-function arguments

=> はほとんどの演算子よりも優先順位が低いので、callback || () がアロー関数の引数リストとして解釈されるのを避けるために括弧が必要になります。

js
callback = callback || (() => {});

基本的な例

js
// 空のアロー関数は undefined を返します
const empty = () => {};

(() => "foobar")();
// "foobar" を返します
// (これは、即時起動型の関数式です。)

const simple = (a) => (a > 15 ? 15 : a);
simple(16); // 15
simple(10); // 10

const max = (a, b) => (a > b ? a : b);

// 簡単な配列のフィルターリング、マッピング等
const arr = [5, 6, 13, 0, 1, 18, 23];

const sum = arr.reduce((a, b) => a + b);
// 66

const even = arr.filter((v) => v % 2 === 0);
// [6, 0, 18]

const double = arr.map((v) => v * 2);
// [10, 12, 26, 0, 2, 36, 46]

// さらに簡潔なプロミスチェーン
promise
  .then((a) => {
    // …
  })
  .then((b) => {
    // …
  });

// 見た目に解析が簡単な引数なしのアロー関数
setTimeout(() => {
  console.log("I happen sooner");
  setTimeout(() => {
    // deeper code
    console.log("I happen later");
  }, 1);
}, 1);

call、apply、bind の使用

callapplybind は、従来の関数ではそれぞれのメソッドにスコープを確立するので、期待通りに動作します。

js
const obj = {
  num: 100,
};

// "num" を window に設定し、使用されていないことを表す。
globalThis.num = 42;

// 単純な従来の関数で "this" を運用する
const add = function (a, b, c) {
  return this.num + a + b + c;
};

console.log(add.call(obj, 1, 2, 3)); // 106
console.log(add.apply(obj, [1, 2, 3])); // 106
const boundAdd = add.bind(obj);
console.log(boundAdd(1, 2, 3)); // 106

アロー関数では、 add 関数は基本的に window (グローバル) スコープで作成されているので、 this は window だと仮定されます。

js
const obj = {
  num: 100,
};

// "num" を window に設定し、どのように扱われるかを見る。
globalThis.num = 42;

// アロー関数
const add = (a, b, c) => this.num + a + b + c;

console.log(add.call(obj, 1, 2, 3)); // 48
console.log(add.apply(obj, [1, 2, 3])); // 48
const boundAdd = add.bind(obj);
console.log(boundAdd(1, 2, 3)); // 48

おそらくアロー関数を使う最大の利点は、 DOM レベルのメソッド(setTimeout()EventTarget.prototype.addEventListener())で、通常は何らかのクロージャ、call()apply()bind() を使用して、関数が適切なスコープで実行されることを確認する必要があることです。

従来の関数式では、このようなコードは期待通りに動作しません。

js
const obj = {
  count: 10,
  doSomethingLater() {
    setTimeout(function () {
      // この関数は window のスコープで実行される
      this.count++;
      console.log(this.count);
    }, 300);
  },
};

obj.doSomethingLater(); // "NaN" と表示。 "count" プロパティが window のスコープにないため。

アロー関数を使えば、 this スコープをより簡単に保持することができます。

js
const obj = {
  count: 10,
  doSomethingLater() {
    // メソッドの構文で "this" を "obj" コンテキストにバインドする
    setTimeout(() => {
      // アロー関数は独自のバインドを行わないので、
      // setTimeout (関数呼び出しとして)は自身へのバインドを作成せず、
      // 外部メソッドの "obj" コンテキストが使用される。
      this.count++;
      console.log(this.count);
    }, 300);
  },
};

obj.doSomethingLater(); // logs 11

仕様書

Specification
ECMAScript Language Specification
# sec-arrow-function-definitions

ブラウザーの互換性

BCD tables only load in the browser

関連情報