アロー関数は、可愛いらしく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 thisthis.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 valueconsole.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 worksconst 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 aroundadd = (b: number): number => {return this.a + b;}}class ExtendedAdder extends Adder {// Create a copy of parent before creating our ownprivate superAdd = this.add;// Now create our overrideadd = (b: number): number => {return this.superAdd(b);}}
時には単純なオブジェクトリテラルを返す関数が必要な場合もあります。しかし、下記のような場合:
// WRONG WAY TO DO ITvar foo = () => {bar: 123};
JavaScriptランタイム(JavaScriptの仕様に原因がある)によってJavaScript Labelを含むブロックとして解釈されます。
この意味がわからなくても心配しないでください。いずれにしろ、"unused label"(未使用のラベル)というTypeScriptのナイスなコンパイラエラーが発生します。Labelは古い(そしてほとんどは使用されていない)JavaScript機能で、現代のGOTO(経験豊富な開発者は悪いものとみなす🌹)として無視して構いません。
()
でオブジェクトのリテラルを囲むことで修正できます:
// Correct 🌹var foo = () => ({bar: 123});