アロー関数は、可愛いらしく_fat arrow_(なぜなら->
は細く =>
は太い)、または_lambda関数_(他の言語でそう名付けられているため)とも呼ばれます。一般的に利用される機能のひとつは、このアロー関数()=> something
です。これを使う理由は次の通りです:
function
を何度も打ち込まなくて済むthis
の意味をレキシカルスコープで捕捉する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
このコードをブラウザで実行すると、関数内のthis
はwindow
を指します。なぜなら、window
がgrowOld
関数を実行しているからです。修正方法は、アロー関数を使うことです:
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
簡潔な構文が得られることに加えて、関数を他の何かに呼び出してもらいたい場合も、アロー関数を使うだけで良いのです。本質的には以下の通りです:
var growOld = person.growOld;
// Then later someone else calls it:
growOld();
自分で呼び出す場合は以下のようになります:
person.growOld();
どちらでも、this
は正しいコンテキストになります(この例ではperson
)。
実は、this
を_呼び出しコンテキストにしたい_場合は_アロー関数を使うべきではありません_。jquery、underscore、mochaのようなライブラリで使用するコールバックのケースです。ドキュメントがthis
の機能について言及している場合は、おそらくアロー関数の代わりにfunction
を使うべきでしょう。同様に、あなたがarguments
を使うことを予定している場合は、アロー関数を使用しないでください。
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
});
クラスのプロパティとしてのアロー関数は、継承において正常に動作します:
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);
}
}
時には単純なオブジェクトリテラルを返す関数が必要な場合もあります。しかし、下記のような場合:
// WRONG WAY TO DO IT
var foo = () => {
bar: 123
};
JavaScriptランタイム(JavaScriptの仕様に原因がある)によって_JavaScript Label_を含む_ブロック_として解釈されます。
この意味がわからなくても心配しないでください。いずれにしろ、"unused label"(未使用のラベル)というTypeScriptのナイスなコンパイラエラーが発生します。Labelは古い(そしてほとんどは使用されていない)JavaScript機能で、現代のGOTO(経験豊富な開発者は悪いものとみなす🌹)として無視して構いません。
()
でオブジェクトのリテラルを囲むことで修正できます:
// Correct 🌹
var foo = () => ({
bar: 123
});