ArrowFunctionのthis: ES.nextとCoffeeScriptとTypeScript
Firefox 22(Nightly) で ArrowFunction が実装されたが、扱いが難しい - hogehoge @teramako
id:teramakoさんの記事を読んで、ES.nextのArrow Functionって単純な省略記法じゃないんだーっとか思ったけど、よく考えたらCoffeeScriptだってfat arrowならthisをbindするわけで、むしろ自然だし便利ですよねと思い直しただけの記事です。
ES.next
var obj = { threshold: 3, getOverThreshold: function (items) { return items.filter(n => n > this.threshold); } };
filterメソッドの中を普通のfunctionで定義しちゃうとthisがglobalオブジェクトになっちゃうんでselfとかbindとか使わないといけないのが、Arrow Functionならthisがobjになるよという話。
つーことはオブジェクトプロパティのfunctionは省略できないのかー!
var obj = { threshold: 3, // ここをArrow Funcitonにすると、 getOverThreshold: (items) => { // getOverThresholdメソッド内のthisがglobal objectになっちゃう。 return items.filter(n => n > this.threshold); } };
と思ったけどES.nextではMethodDefinitionの省略記法が使えるようになる(実装ブラウザはまだない?)からそこは杞憂ということですね。
var obj = { threshold: 3, getOverThreshold(items) { return items.filter(n => n > this.threshold); } };
クラスのメソッド定義も同じような感じでfunctionキーワード不要なので、要するに普通に書いてたらArrow Functionを使うべきか?とかほとんど考えないで良さそう。
CoffeeScript
CoffeeScriptには2種類のArrow Functionがあって使い分ける必要があります。
->
: functionの本当のシンタックスシュガー=>
: Fat Arrow。thisをbindするfunction定義 。ES.nextに近い動き*1。
// Source obj = threshold: 3, getOverThreshold: (items) -> // Thin Arrow items.filter (n) => n > this.threshold // Fat Arrow // Compiled JavaScript var obj; obj = { threshold: 3, getOverThreshold: function(items) { var _this = this; return items.filter(function(n) { return n > _this.threshold; }); } };
両方fatにしちゃうとうまくいかない。
// Source obj = threshold: 3, getOverThreshold: (items) => items.filter (n) => n > this.threshold // Compiled JavaScript var obj, _this = this; // うわーん>< obj = { threshold: 3, getOverThreshold: function(items) { return items.filter(function(n) { return n > _this.threshold; }); } };
なので意識しないといけない。CoffeeScriptでがりがりロジック書いてる人は、この辺のtypoって結構あったりするのかな?
TypeScript
ES.nextを先取り実装してると評判のTypeScript。Arrow FunctionもES.nextに近いタイプで、thisをbindします。というか吐き出すコードは完全にCoffeeScriptと一緒。 (2013/03/25更新: 初出時ソースが微妙に間違ってましたスミマセン)
// Source var obj = { threshold: 3, getOverThreshold: function(items: number[]): number[] { return items.filter(n => n > this.threshold); } }; // Compiled JavaScript var obj = { threshold: 3, getOverThreshold: function (items) { var _this = this; return items.filter(function (n) { return n > _this.threshold; }); } };
オブジェクトプロパティの定義もArrowにしてみても変化なし。
// Source var obj = { threshold: 3, getOverThreshold: (items: number[]): number[] => { return items.filter(n => n > this.threshold); } }; // Compiled JavaScript var obj = { threshold: 3, getOverThreshold: function (items) { var _this = this; return items.filter(function (n) { return n > _this.threshold; }); } };
メソッド内でthis使ってみたら、頭にthisのbindが出てきました。が、メソッド内でthisの再bindをしているので挙動は変化なし。
// Source var obj = { threshold: 3, getOverThreshold: (items: number[]): number[] => { console.log(this.threshold); // thisを参照する。 return items.filter(n => n > this.threshold); } }; // Compiled JavaScript var _this = this; // なんか出てきた! var obj = { threshold: 3, getOverThreshold: function (items) { var _this = this; // でもここで再定義。 console.log(_this.threshold); return items.filter(function (n) { return n > _this.threshold; }); } };
この場合は動くけど、やっぱり怪しいから使わない方が良さげ。 TypeScriptはECMAScriptに追随するっぽいので、いずれメソッド定義の省略記法がそのうち使えるようになるんじゃないでしょうか(ES.nextに採用されれば)。