以前書いた記事でオブジェクトの関数内でsetTimeout()
に無記名関数を渡した際のthisの値について理解が不明瞭だったのでjavascriptの関数とthisについて調べたメモです。
関数について
関数宣言 (function declaration)
function 関数名(){}
関数の巻き上げが発生する
!function() { var res = add(3, 4); // 7 function add(n1, n2) { return n1 + n2; } }();
関数宣言はスコープの先頭まで巻き上げられるので、上記の記述は下記の解釈となる
!function() { function add(n1, n2) { return n1 + n2; } var res = add(3, 4); // 7 }();
関数式 (funtion expression)
var 関数名 = function(){}
※ 関数式の場合関数名は必須ではない。(無記名関数)
関数の巻き上げは発生しない
!function() { var res = add(3, 4); // TypeError: add is not a function var add = function(n1, n2) { return n1 + n2; } }();
thisについて
Javascriptの関数は その関数を呼び出しているオブジェクトを表す
this
オブジェクト を持っている。
[出展]: オブジェクト指向JavaScriptの原則
Strictモードでの違い
this
の値にnull
やundefined
が設定されている時
- 標準モード
this
= グローバルオブジェクト - Strictモード
this
=null
・undefined
の値のまま
標準モード
function showThis() { console.log(this); } showThis(); // => global Object (Window)
Strictモード
'use strict'; function showThis() { console.log(this); } showThis(); // => undefined
グローバススコープではthis
はグローバルオブジェクトと指す。= WEBブラウザではWindow
。
グローバル変数はグローバルオブジェクのプロパティとして扱われる。
"use strict"; console.log(this); // Window // window.a = 'Hibiki'; と同じ意味 var a = 'Hibiki'; function say() { var a = 'Abukuma'; console.log(a); // "Abukuma" console.log(window.a); // "Hibiki" } say();
オブジェクトのメソッドとして定義してある関数内のthis
は通常そのオブジェクト自身
var obj = { name: 'サーバル', say: function() { // `this` is obj console.log(this, this.name + 'ちゃんすごーい!'); } }; obj.say(); // Object, "サーバルちゃんすごーい!"
this
の値は関数が呼び出される際に設定される
function sayThisName() { console.log(this.name); } var obj1 = { name: 'かばん', sayName: sayThisName }; var obj2 = { name: 'サーバル', sayName: sayThisName }; var name = 'ミライ'; obj1.sayName(); // かばん obj2.sayName(); // サーバル // 標準モード: `this` is Window => "ミライ" // Strict Mode: Error `this` is undefined sayThisName();
setTimeoutのcallback関数のthis
setTimeout()
はwindow
オブジェクトが持っているメソッドなので省略せずに書くとwindow.setTimeout()
var obj = { name: 'MyObject', fnc: function() { var self = this; // MyObject window.setTimeout(function() { console.log(self); // => MyObject console.log(this); }, 1000); } };
setTimeout
が書かれているのと同じスコープにある変数が実行されるcallback関数の中でも使えるので、
setTimeout
の第一引数に無記名関数を渡すのは👇と同じなのではないかと考えています。(厳密にはどうか解りませんが...)
var obj = { name: 'MyObject', fnc: function() { var self = this; // MyObject var callback = function() { console.log(self); // => MyObject console.log(this); }; window.setTimeout(callback, 1000); } };
なので、setTimeout
に渡されるコールバック関数は通常の関数と同じだと考えていました。
オブジェクト内の関数内に関数を作成して呼び出した場合のthis
var obj = { name: 'MyObjct', fnc: function() { console.log(this); // MyObject // 関数宣言 function objFnc () { console.log(this); // 標準モード => Global Object[window] // Strict Mode => undefined } objFnc(); // 関数式 var myFnc = function() { console.log(this); // 標準モード => Global Object[window] // Strict Mode => undefined }; myFnc(); // 即時関数 (function() { console.log(this); // 標準モード => Global Object[window] // Strict Mode => undefined })(); } }; obj.fnc();
オブジェクト内の関数内に関数を作成して実行しても、その関数を呼び出しているオブジェクトは無いので関数内のthis
はStrictモードならundefined
、標準モードならグローバルオブジェクト(WEBブラウザならwindow
)になるので、Strict
モードならcallback関数のthis
はundefined
になるのではないかと思っていたのですが、実際は👇
var obj = { name: 'MyObject', fnc: function() { var self = this; // MyObject var callback = function() { console.log(self); // => MyObject console.log(this); // 標準モード => Global Object[window] // Strict Mode => Global Object[window] }; window.setTimeout(callback, 1000); } };
コールバック関数内のthis
は標準モードでもStrictモードでも window
となっていました。
これはsetTimeout
関数のcallbackはwindow
がcallback関数を呼び出す仕様(applyやcallで実行されるなどthis
がwindow
になる仕様)になっているという事なのだと思います。
setTimeout() によって実行されるコードは、setTimeout() が呼び出された関数とは別の実行コンテキスト内で実行されます。結果的に、呼び出された関数の this キーワードは window (または global) オブジェクトに設定され、setTimeout が呼び出された関数の this 値と同じにはなりません。
WindowOrWorkerGlobalScope.setTimeout() - Web API | MDN
ドキュメント見に行けば済んでいた話でした。
Strictモードと標準モードで違いがあることを下手に知っていたために混乱してしまっていましたorz
追記
window.setTimeout
で実行されるthisがwindowになるのはsetTimeoutを呼出しているオブジェクトが指定されるからではないか?と思い、call
やapply
でsetTimeout
を呼び出すとどうなるのか試して見たところ...
var fnc = function() { console.log(this); }; window.setTimeout.call(null, fnc, 100); // Uncaught TypeError: Illegal invocation window.setTimeout.apply(null, [fnc, 100]); // Uncaught TypeError: Illegal invocation var _setTimeout = window.setTimeout; _setTimeout.call(null, fnc, 100); // Uncaught TypeError: Illegal invocation _setTimeout.apply(null, [fnc, 100]); // Uncaught TypeError: Illegal invocation
どうやらこの動作は許可されない様です...
[参考]
- WindowOrWorkerGlobalScope.setTimeout() - Web API | MDN
- this - JavaScript | MDN #The bind method
- WindowOrWorkerGlobalScope.setTimeout() - Web APIs | MDN
- あまり誰も教えてくれないネタjavascript小話 - Qiita

- 作者: Nicholas C. Zakas,和田祐一郎
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/06/18
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る