追記
当初 ES7 の記法を使っていて ES6 で動作しなかったため記事を修正しました。ご指摘ありがとうございました。
JavaScript の this は変更可能なためクラスのインスタンスメソッドが呼び方によっては期待した動作をしないことがあります。
インクリメントメソッドを色々呼び出していますが、 this を変更する呼び方がエラーになります。
class Counter {
constructor() { this.count = 0; }
increment() { ++this.count; }
}
let counter = new Counter;
counter.increment(); // OK
let increment = counter.increment;
increment(); // Error
counter.increment.call(null); // Error
counter.increment.bind(null)(); // Error
そういう呼び方をしなければすむ話なんですが、たまにそういう呼び方をしても正常に動作させたいときがあります。
そういうときはインクリメントメソッドをこう書き換えます。
class Counter {
constructor() {
this.count = 0;
this.increment = () => { ++this.count; }; // Changed
}
}
let counter = new Counter;
counter.increment(); // OK
let increment = counter.increment;
increment(); // OK
counter.increment.call(null); // OK
counter.increment.bind(null)(); // OK
console.log(counter.count); // 4
ちゃんとエラーにならず4回インクリメントされました。
なぜ正常に動作するのかというとアロー関数で this を関数に束縛したので this を変更する呼び方をしても this が変わらないためです。
Babel の吐くソースがわかりやすいです。
普通のメソッド定義
this をそのまま参照しているため this の変更の影響を受けます。
_createClass(Counter, [{
key: "increment",
value: function increment() {
++this.count;
}
}]);
アロー関数を使ったメソッド定義
this をローカル変数に受けてクロージャで関数に束縛しています。インクリメントメソッドはインスタンス生成時の this 、つまりインスタンス自身を参照し続けるため this の変更の影響を受けないのです。
var _this = this;
this.increment = function () {
++_this.count;
};
まとめ
this が変わりうる
class { increment() { ++this.count; } }
this が変わらない
class {
constructor() {
this.increment = () => { ++this.count; };
}
}
ES6 のクラスサンプルにこれがあるのを見たことがない(アロー関数を使っているだけでクラスの記法として新しくも特別なわけでもないので仕方ない)けれど知らない人は気づきにくいと思ったので書きました。
おまけ
この話はメソッドに限らないので関数とアロー関数、
function() {}
() => {}
オブジェクトメソッドとそのアロー関数、
{
hoge() {},
fuga: () =>
}
でも同じことが言えるのでアロー関数が定義されたスコープの this を束縛する点に留意して使い分けることができます。