アロー関数

アロー関数式は、より短く記述できる、通常の function 式の代替構文です。また、this, arguments, super, new.target を束縛しません。アロー関数式は、メソッドでない関数に最適で、コンストラクタとして使うことはできません。

構文

基本的な構文

(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
// 上記の式は、次の式と同等です: => { return expression; }

// 引数が 1 つしかない場合、丸括弧 () の使用は任意です:
(singleParam) => { statements }
singleParam => { statements }

// 引数がない場合、丸括弧を書かねばいけません:
() => { statements }

高度な構文

// object リテラル式を返す場合は、本体を丸括弧 () で囲みます:
params => ({foo: bar})

// 残余引数デフォルト引数 をサポートしています
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => {
statements }

// 引数リスト内の分割代入もサポートしています
var f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f(); // 6

説明

hacks.mozilla.org の "ES6 In Depth: Arrow functions" も参照してください。

2 つの理由から、アロー関数が導入されました。1 つ目の理由は関数を短く書きたいということで、2 つ目の理由は this を束縛したくない、ということです。

関数の短縮形

var elements = [
  'Hydrogen',
  'Helium',
  'Lithium',
  'Beryllium'
];

elements.map(function(element) {
  return element.length;
}); // このステートメントが返す配列: [8, 6, 7, 9]

// 上記の通常の関数は、以下のアロー関数として記述できます
elements.map((element) => {
  return element.length;
}); // [8, 6, 7, 9]

// パラメータが一つしか無い場合、周囲の括弧を削除できます:
elements.map(element => {
  return element.length;
}); // [8, 6, 7, 9]

// アロー関数の唯一のステートメントが `return` の場合、 `return` を削除し
// 周囲の波括弧も削除できます
elements.map(element => element.length); // [8, 6, 7, 9]

// この場合、必要なのは length property のみなので、分割パラメータを使用できます:
// 文字列 `"length"` は取得したいプロパティに対応しますが
// 明らかに特別でない `lengthFooBArX` は、任意の有効な変数名に
// 変更可能な変数名です
elements.map(({ "length": lengthFooBArX }) => lengthFooBArX); // [8, 6, 7, 9]

// この分割パラメータの代入は、以下のように記述することも可能です。ただし、この例では、
// 作成されたプロパティに `length` の値を代入していないことに注意して下さい。代わりに、
// 変数 `length` のリテラル名自体が、オブジェクトから取得するプロパティとして使用されます。
elements.map(({ length }) => length); // [8, 6, 7, 9]

this を束縛しない

アロー関数以前は、関数の呼び出し方法に応じて自身の this 値を定義していました

  • コンストラクタでは新しいオブジェクト
  • strict モード の関数呼び出しでは undefined
  • 「オブジェクトのメソッド」として呼び出された関数ではそのときのオブジェクト
  • など

これは、オブジェクト指向プログラミングをする上で煩わしいということが分かりました。

function Person() {
  // Person() のコンストラクタは、自分のインスタンスを `this` として定義する。
  this.age = 0;

  setInterval(function growUp() {
    // 非 strict モードでは、growUp() 関数は `this` を
    // グローバルオブジェクトとして定義する。
    // (そこで grouUp()が実行されているため)
    // Person() コンストラクタが定義した `this` とは違う。
    this.age++;
  }, 1000);
}

var p = new Person();

ECMAScript 3/5 では、この問題は this の値をスコープ内の変数に代入することで解決できました。

function Person() {
  var that = this;
  that.age = 0;

  setInterval(function growUp() {
    // このコールバックは、期待されるオブジェクトの値を
    // `that` 変数で参照する。
    that.age++;
  }, 1000);
}

あるいは、適切な this の値を対象の関数(上の例では growUp() 関数)に渡すように、束縛関数を作成することもできました。

アロー関数自身は this を持ちません。レキシカルスコープの this 値を使います。つまり、アロー関数内の this 値は通常の変数検索ルールに従います。このためスコープに this 値がない場合、その一つ外側のスコープで this 値を探します。

そのため、次のコードで setInterval に渡される関数の this の値は、外部関数の this と同じ値になります:

function Person(){
  this.age = 0;

  setInterval(() => {
    this.age++; // |this| は person オブジェクトを適切に参照します。
  }, 1000);
}

var p = new Person();

strict モードとの関連

this がレキシカルなもので与えられる場合、strict モードthis に関する規則は無視されます。

var f = () => { 'use strict'; return this; };
f() === window; // またはグローバルオブジェクト

他の strict モードの規則は通常通り適用されます。

call や apply からの呼び出し

アロー関数は自身で this を持たないので、call()apply() メソッドは引数しか渡せません。this は無視されます。

var adder = {
  base: 1,

  add: function(a) {
    var f = v => v + this.base;
    return f(a);
  },

  addThruCall: function(a) {
    var f = v => v + this.base;
    var b = {
      base: 2
    };

    return f.call(b, a);
  }
};

console.log(adder.add(1));         // 2 を出力する
console.log(adder.addThruCall(1)); // やはり 2 を出力する

arguments を束縛しない

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

var arguments = [1, 2, 3];
var arr = () => arguments[0];

arr(); // 1

function foo(n) {
  var f = () => arguments[0] + n; // foo は arguments を暗黙的に束縛している。arguments[0] は n である。
  return f();
}

foo(3); // 6

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

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

foo(1); // 11

メソッドとして使われるアロー関数

前に述べたように、アロー関数式は非メソッド型の関数に最もよく合っています。これをメソッドとして使った時のことを見てみましょう:

'use strict';

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

obj.b(); // prints undefined, Window {...} (or the global object)
obj.c(); // prints 10, Object {...}

アロー関数は自身の this を持ちません。Object.defineProperty() を使う例です。

'use strict';
var obj = {
  a: 10
};

Object.defineProperty(obj, 'b', {
  get: () => {
    console.log(this.a, typeof this.a, this); // undefined 'undefined' Window {...} (or the global object)
    return this.a + 10; // represents global object 'Window', therefore 'this.a' returns 'undefined'
  }
});

new 演算子の使用

アロー関数はコンストラクタとして使用できず、new と共に使うとエラーになります。

var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor

prototype プロパティの使用

アロー関数には prototype プロパティはありません。

var Foo = () => {};
console.log(Foo.prototype); // undefined

yield キーワードの使用

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

関数の Body 部分

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

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

var func = x => x * x;
// 簡潔構文の場合、明示せずとも"return" されます
var func = (x, y) => { return x + y; };
// ブロック文体では、明示的に "return" を宣言する必要があります

オブジェクトリテラルを返す

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

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

var func = () => { foo: function() {} };
// SyntaxError: function 文には名前が必要

これは、括弧 ({}) 内のコードが文の列として構文解析されてしまっているからです(つまり、foo はオブジェクトリテラル内のキーでなく、ラベルとして扱われています)。

オブジェクトリテラルは括弧で囲むのを忘れないでください。

var func = () => ({ foo: 1 });

改行

アロー関数には括弧とアロー(矢印)の間に改行を入れられません。

var func = ()
           => 1;
// SyntaxError: expected expression, got '=>'

しかし、下記の例は、アローの後に改行を入れたり、括弧を使って、更に引数の内側で改行を使うことで、綺麗で柔らかなコードに修正できることを確認しています。引数の途中に改行を入れることもできます。

var func = (a, b, c) =>
  1;

var func = (a, b, c) => (
  1
);

var func = (a, b, c) => {
  return 1
};

var func = (
  a,
  b,
  c
) => 1;

// no SyntaxError thrown

解析の順序

アロー関数内のアロー(矢印)はオペレーターではないですが、アロー関数は通常の関数と異なり、オペレーターを引き継いだ特別な解析ルールを持ちます。

let callback;

callback = callback || function() {}; // ok

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

callback = callback || (() => {});    // ok

さらなる例

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

(() => 'foobar')();
// "foobar" を返します
// (this is an Immediately Invoked Function Expression)

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

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

// 簡単な配列のフィルターリング、マッピング等

var arr = [5, 6, 13, 0, 1, 18, 23];

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

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

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

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

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

仕様

ブラウザーの実装状況

BCD tables only load in the browser

関連項目