例 オブジェクト内の関数でsetTimeoutを使ってその中で、オブジェクト自身をthis
として使いたいような場合
var obj = { name: 'obj', func: function() { console.log(this); // => Object setTimeout(function() { console.log(this, this.name); // => Window, "result" }, 100); } }; obj.func();
そのまま書くと、setTimeoutで呼び出された関数のthis
はWindowになる
Functionが参照型だから、thisがwindowになってる? undifined
ではなくwindow
になる理由の理解がまだ不明瞭。
伝統的な方法
var obj = { name: 'obj', func: function() { var self = this; setTimeout(function() { console.log(self, self.name); // => Object, "obj" }, 100); } }; obj.func();
this
をスコープ内にself
や_this
といった別の変数に割り当てて関数内で変数が定義されていなければ上のスコープから変数を探すjavascriptの仕様を利用する方法。
(self = this
はobj
への参照を変数に格納してるって理解で合ってる?)
jQuery $.proxy() を使う方法
var obj = { name: 'obj', func: function() { setTimeout($.proxy(function() { console.log(self, self.name); // => Object, "obj" }, this), 100); } }; obj.func();
jQuery.proxy( function, context )context
Type: PlainObject
The object to which the context (this
) of the function should be set.
jQuery.proxy() | jQuery API Documentation
$.proxy()
は第二引数に第一引数のFunction内で this
として扱うものを指定できる。
ES5 Function.prototype.bind
を使う方法
'use strict'; var obj = { name: 'obj', func: function() { setTimeout(function() { console.log(this, this.name); // => Object, "obj" }.bind(this), 100); } }; obj.func();
Function.prototype.bind()
bind() メソッドは、呼び出された時に新しい関数を生成します。最初の引数 thisArg は新しい関数の this キーワードにセットされます。2 個目以降の引数は、新しい関数より前に、ターゲット関数の引数として与えられます。
fun.bind(thisArg[, arg1[, arg2[, ...]]])thisArg
束縛された関数が呼ばれる時、this 値としてターゲット関数に渡される値を指定します。束縛された関数が new 演算子によって構築された場合、この引数は無視されます。
Function.prototype.bind() - JavaScript | MDN
function(){}.bind(<関数内でthisとして使いたいもの>)
の様な感じでFunctionのthisを拘束する。
下記のような使い方もできる。
'use strict'; var obj = { name: 'obj', say: function() { console.log( this, this.name ); } }; setTimeout( obj.say.bind(obj), 100 ); // => Object, `obj`
[参考] es5のサポート状況
ES6 アロー関数() =>
を使う
まだIE11とかまだサポートされていないブラウザもありますが、ES6のアロー関数を使えば関数内のthis
は呼出し元のthis
が保証されるという事のようなので、次のように書く事ができます。
'use strict'; var obj = { name: 'obj', func: function() { setTimeout( () => { console.log(this, this,name); // => Object, "obj" }, 100); } }; obj.func();
[参考] es6のサポート状況
アロー関数 - JavaScript | MDN
this を束縛しない
//(中略)
アロー関数がスコープ内の this の値を捕捉するため、
//(略)
これを読む限りアロー関数は関数が定義されているスコープを関数内のthis
として使用する という事、つまり「thisが何を指すかは関数定義時に決まる」のではないかと思います。(間違ってたら指摘ください
ES5 から ES6 へ書き換える際の アロー関数 の落とし穴
アロー関数は関数を定義した際にそのスコープをthis
として束縛するようなので、いままで function()
で書いていたものをそのまま () =>
に書き換えると思わぬ落とし穴にハマってしまうことがあります。
例えば下記のようなコード
// ES5 'use strict'; var obj = { name: 'obj', callback: function() { console.log(this, this.name); }, func: function() { setTimeout( this.callback.bind(this), 100 ); }, }; obj.func(); // => Object "obj"
これを単純にアロー関数に書き換えると... 👇
// ES6 'use strict'; var obj = { name: 'obj', callback: () => { console.log(this, this.name); }, func: () => { setTimeout( this.callback, 100 ); }, }; obj.func(); // => consoleには何も表示されない
これは下記と書き方と同等かと思います。({}
はスコープを作成しない...はず)
var obj = {}; // new Object(); でも同じ? obj.name = 'obj'; obj.callback = () => console.log(this, this.name); obj.func = () => setTimeout( this.callback, 100 );
なので、アロー関数で定義されたobj.callback
、obj.func
内の this
が obj
ではなく、Window(グローバルオブジェクト)に拘束されてしまっているので上手く動作しなかったのだと考えられます。
この場合は、
アロー関数を使わずに、ES5のFunction.prototype.bind()
を使って obj.callback
関数を呼び出す
'use strict'; var obj = { name: 'obj', callback: function() { console.log(this, this.name); }, func: function() { setTimeout( this.callback.bind(this), 100 ); }, }; obj.func(); // => Object "obj"
又は
this
をobj
に拘束してあるアロー関数内から obj.callback
関数を呼び出す
var obj = { name: 'obj', callback: function() { console.log(this, this.name); }, func: function() { setTimeout( () => this.callback(), 100 ); } }; obj.func(); // => Object "obj"
の様な書き方にすれば上手く動作します。
クラスのようなprototypeにメソッドを付けている様な場合も同様のようです。
だめな例
'use strict'; var MyObject = function() { this.name = 'obj'; return this; }; MyObject.prototype.callback = () => { console.log(this, this.name); // this が Windowになってしまう } MyObject.prototype.func = () => { setTimeout( this.callback, 100); // this が Windowになってしまう }; var obj = new MyObject(); obj.func(); // =>
callback
、func
内の this
が Window(グローバルオブジェクト)になるのでうまく動作しない。
動作する例
'use strict'; var MyObject = function() { this.name = 'obj'; return this; }; MyObject.prototype.callback = function() { console.log(this, this.name); } MyObject.prototype.func = function() { setTimeout( this.callback.bind(this), 100); // 又は // setTimeout( ()=>this.callback(), 100); }; var obj = new MyObject(); obj.func(); // => MyObject, "obj"
関数の定義をコンストラクタ内などに記述すればアロー関数で定義することもできました。
'use strict'; var MyObject = function() { this.name = 'obj'; this.callback = () => { console.log(this, this.name); }; this.func = () => { setTimeout(this.callback, 100); } return this; }; var obj = new MyObject(); obj.func(); // => MyObject, "obj"
※ 但し継承させたオブジェクトなどで試していないので、この書き方はもしかすると継承させたオブジェクトからだとアロー関数内のthis
が継承させたオブジェクトにならないなど不具合が出る可能性がありそうな気がしています。
まとめ
ES5もES6もちゃんと学習をしていなくて、ES6にしたら関数はすべてアロー関数() =>
にするのかな?のような曖昧な理解だったので、ES6はfunction()
と()=>
を挙動の違いを理解して適材適所に共存させて使うんだ!と今回調べてみて少し理解が深まったように思います。
溜まっていたを技術書少しづづ読んでいます。
今読んでいる「オブジェクト指向JavaScriptの原則」という本は薄めの本ですが、あやふやなまま使っていたECMAScript5で導入されたことをベースに書かれているようなのでES6の前にしっかり読んで理解しておきたいと思ってます。
[参考]
- jQuery.proxy() | jQuery API Documentation
- ECMAScript 5 compatibility table
- ECMAScript 6 compatibility table
- 作者: Nicholas C. Zakas,和田祐一郎
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/06/18
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る