Arrow Functions

アロー関数(Arrow Functions)

アロー関数はfat arrow(なぜなら->は細い矢印で =>は太い矢印であるため)、またはlambda関数(他の言語にならって)と呼ばれています。もう1つの一般的に使用される機能は、アロー関数()=> 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呼び出しコンテキスト(calling context)にしたい場合はアロー関数を使うべきではありません。jquery、underscore、mochaなどのライブラリで使用されるコールバックのケースです。ドキュメントがthisの機能について言及している場合は、おそらくアロー関数の代わりにfunctionを使うべきでしょう。同様に、argumentsを使う場合は、アロー関数を使用しないでください。

Tip: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
});