アロー関数

アロー関数(Arrow Functions)

アロー関数は、可愛いらしくfat arrow(なぜなら->は細く =>は太い)、またはlambda関数(他の言語でそう名付けられているため)とも呼ばれます。一般的に利用される機能のひとつは、このアロー関数()=> somethingです。これを使う理由は次の通りです:

  1. functionを何度もタイプしなくて済む

  2. thisの参照をレキシカルスコープで捕捉する

  3. argumentsの参照をレキシカルスコープで捕捉する

関数型言語と比べると、JavaScriptではfunctionを頻繁に打ち込まなければならない傾向があります。アロー関数を使うと、関数をシンプルに作成できます

var inc = (x)=>x+1;

thisはJavaScriptにおいて昔から頭痛の種でした。 ある賢い人は「thisの意味をすぐに忘れるJavaScriptが嫌いだ」と言いました。アロー関数は、それを囲んだコンテキストからthisを捕捉します。純粋なJavaScriptだけで書かれたクラスを使って考えてみましょう:

function Person(age) {
this.age = age;
this.growOld = function() {
this.age++;
}
}
var person = new Person(1);
setTimeout(person.growOld,1000);
setTimeout(function() { console.log(person.age); },2000); // 1, should have been 2

このコードをブラウザで実行すると、関数内のthiswindowを参照します。なぜなら、windowgrowOld関数を実行しているからです。修正方法は、アロー関数を使うことです:

function Person(age) {
this.age = age;
this.growOld = () => {
this.age++;
}
}
var person = new Person(1);
setTimeout(person.growOld,1000);
setTimeout(function() { console.log(person.age); },2000); // 2

これがうまくいく理由は、アロー関数が、関数本体の外側のthisを捕捉するからです。次のJavaScriptコードは同等の動きをします(TypeScriptを使用しない場合の書き方です)。

function Person(age) {
this.age = age;
var _this = this; // capture this
this.growOld = function() {
_this.age++; // use the captured this
}
}
var person = new Person(1);
setTimeout(person.growOld,1000);
setTimeout(function() { console.log(person.age); },2000); // 2

TypeScriptを使っているので、はるかに快適な構文で書けます。アロー関数とクラスは、組み合わせて利用できます:

class Person {
constructor(public age:number) {}
growOld = () => {
this.age++;
}
}
var person = new Person(1);
setTimeout(person.growOld,1000);
setTimeout(function() { console.log(person.age); },2000); // 2

このパターンについてのSweetなビデオ🌹

Tip:アロー関数の必要性

簡潔な構文が得られることに加えて、関数を他の何かに呼び出してもらいたい場合も、アロー関数を使うだけで良いのです。本質的には以下の通りです:

var growOld = person.growOld;
// Then later someone else calls it:
growOld();

自分で呼び出す場合は以下のようになります:

person.growOld();

どちらでも、thisは正しいコンテキストになります(この例ではperson)。

Tip:アロー関数の危険性

実は、this呼び出しコンテキストにしたい場合はアロー関数を使うべきではありません。jquery、underscore、mochaのようなライブラリで使用するコールバックのケースです。ドキュメントがthisの機能について言及している場合は、おそらくアロー関数の代わりにfunctionを使うべきでしょう。同様に、あなたがargumentsを使うことを予定している場合は、アロー関数を使用しないでください。

Tip:thisを使用するライブラリのアロー関数

thisを使う多くのライブラリ、例えばjQueryの反復(例: https://api.jquery.com/jquery.each/)はthisを使って現在反復中のオブジェクトを渡します。このようなケースにおいて、ライブラリが渡したthisだけでなく周囲のコンテキストにもアクセスしたい場合は、アロー関数が無いときに行うように_selfのような一時変数を使用してください。

let _self = this;
something.each(function() {
console.log(_self); // the lexically scoped value
console.log(this); // the library passed value
});

Tip:アロー関数と継承

クラスのプロパティとしてのアロー関数は、継承において正常に動作します:

class Adder {
constructor(public a: number) {}
add = (b: number): number => {
return this.a + b;
}
}
class Child extends Adder {
callAdd(b: number) {
return this.add(b);
}
}
// Demo to show it works
const child = new Child(123);
console.log(child.callAdd(123)); // 246

しかし、子クラスで関数をオーバーライドした場合、superキーワードは動作しません。プロパティはthisに行きます。thisは1つしかないので、このような関数は、親クラスの同じ関数を呼び出すことはできません(superはprototypeのメンバだけで動作します)。メソッドを子にオーバーライドする前に、メソッドのコピーを作成することで回避できます。

class Adder {
constructor(public a: number) {}
// This function is now safe to pass around
add = (b: number): number => {
return this.a + b;
}
}
class ExtendedAdder extends Adder {
// Create a copy of parent before creating our own
private superAdd = this.add;
// Now create our override
add = (b: number): number => {
return this.superAdd(b);
}
}

Tip:Quick Object Return

時には単純なオブジェクトリテラルを返す関数が必要な場合もあります。しかし、下記のような場合:

// WRONG WAY TO DO IT
var foo = () => {
bar: 123
};

JavaScriptランタイム(JavaScriptの仕様に原因がある)によってJavaScript Labelを含むブロックとして解釈されます。

この意味がわからなくても心配しないでください。いずれにしろ、"unused label"(未使用のラベル)というTypeScriptのナイスなコンパイラエラーが発生します。Labelは古い(そしてほとんどは使用されていない)JavaScript機能で、現代のGOTO(経験豊富な開発者は悪いものとみなす🌹)として無視して構いません。

()でオブジェクトのリテラルを囲むことで修正できます:

// Correct 🌹
var foo = () => ({
bar: 123
});