JavaScriptにおけるthisの挙動まとめ
はじめに
-
混乱しまくったのでまとめたのですが、予想以上にまとめが大変だったので、間違っていればご指摘くだされば幸いです。
-
Node.js v7.1.0で検証しました。
表まとめ
- thisは呼び出しのパターンによって異なる:
パターン | thisの内容 | |
---|---|---|
0 | 直にthisを呼び出し | module.exportsオブジェクト |
1 | (インスタンス)メソッド呼び出し | レシーバーのオブジェクト自体 |
2 | 関数呼び出し | 内部(入れ子)関数: グローバルオブジェクト 外部関数:その関数が属すオブジェクト |
3 | コンストラクタ呼び出し | new演算子で生成:新しく生成されるインスタンス |
4 | call, apply呼び出し | 第一引数で自由に指定できる |
5 | アロー演算子(ES2015)による呼び出し | アロー関数自体が属するオブジェクト |
6 | prototypeメソッド呼び出し | 生成されたインスタンス |
7 | イベントリスナー呼び出し | イベントの発生源 |
(8) | 静的メソッド呼び出し | 関数オブジェクト自体 |
0 : 直接thisを呼び出す
Node.js特有ですが、module.exportオブジェクトに関しては、
http://nodejs.jp/nodejs.org_ja/docs/v0.6/api/globals.html#module
をご覧ください1。
各種ブラウザだとWindowオブジェクトになります。
console.log(this);//{} : module.exports Object
this.obj = "a"
console.log(this); // { obj: 'a' }
//cf) 2 : 関数呼び出しも参考にしてください
var func = function()
{
console.log(this);
};
func(); //thisはグローバルオブジェクトを指す。
1~4まで(メソッド、関数、コンストラクタ、call,apply)
1~4までは、基本の説明はJavaScript The Good Parts の4章に詳しく書いてあるので、読んでいただければ2。
自分が混乱した部分について、いくつか補足します。
- 2 : 関数呼び出し の「その関数が属すオブジェクト」とは、こういう意味です:
var obj1 =
{
p0 : this,
//外部関数
p1 :function() {return this; },
//入れ子(内部)関数
p2 : function() { return function() {return this;} },
};
//thisはmodule.exportオブジェクトをさす
console.log(obj1.p0); //{}
//thisはobj1をさす
console.log(obj1.p1());
//thisはグローバルオブジェクトをさす(上記の本の4.3.2には、「言語の設計としては失敗」と書いてある!)
console.log(obj1.p2()());
念のためですが、Objectコンストラクタを用いても、結果は全く同じです。
var obj1 = new Object();
obj1.p0 = this;
obj1.p1 = function() { return this;};
obj1.p2 = function() { return function() { return this; }};
- 4 : call, apply呼び出し について。よく、callとか第一引数にthisを入れたりしますが、単純に2に該当するので、以下のコードの場合、グローバルオブジェクトを指します:
//call関数の引数thisはグローバルオブジェクトをさす
//console.log内の引数のthisは(インスタンス)メソッド呼び出しに該当するので、
//同様にレシーバーのオブジェクト(つまりグローバルオブジェクト)となる
(function () {
console.log(this);
}).call(this);
なので、意味合い的には、
グローバルオブジェクトはこの無名関数をメソッドとして持っていないけれども、call関数を使用することによって、無名関数をあたかもグローバルオブジェクトのメソッドとして呼び出している
みたいな感じになります。
なんでそんなめんどくさいことをするのかは、strict modeとの兼ね合いがあるのですが、例えば、http://qiita.com/osakanafish/items/7c0240f195f36e66b101 をご覧ください。
- 下記の例のように、コンストラクタをnewをつけずに普通の関数として呼び出してしまった時は、thisは2のパターンでの呼び出しに該当するので、thisはグローバルオブジェクトを指してしまいます3。
var Member = function()
{
this.name= 'a';
}
//関数とみなして呼び出すと,thisがグローバルオブジェクトとなる。
var inst = Member();
//thisはグローバルオブジェクトをさしてしまうため、nameが参照できてしまう
console.log(name);
5について(アロー関数)
5のアロー関数呼び出しについては、アロー関数自身が宣言された場所に依って決まります。3と非常によく似ていますが、下記のp1とp1_2メソッドにおいて挙動の相違が確認出来ます:
var obj2 =
{
p0 : this, //{}
p1 : function() {return this; }, //obj2をさす.上記参照
//p0と状況が同じ。
p1_2 : () => {return this;},
//入れ子になっているが、p0と状況は同じ。
p2 : () => { return () => {return this; } },
//アロー関数自体はfunction()内部にあるので、p1と状況は同じ
p3 : function() { return () => {return this; }},
//アロー関数を入れ子関数にしてみる
p4 : function() { return () => { return () => {return this; }; } },
};
//thisはmodule.exportオブジェクトをさす(関数リテラル表現のときと挙動が違うことに注意。)
console.log(obj2.p1_2());
//thisはmodule.exportオブジェクト{}をさす
console.log(obj2.p2()());
//thisはobj1をさす
console.log(obj2.p3()());
//thisはobj1をさす(つまり、p3の時のthisと同じ参照先。)
console.log(obj2.p4()()());
ちなみに、コンストラクタを定義する際はアロー演算子は使えません。ご注意を4。
6について(prototypeメソッド呼び出し)
これは直感的なような気がします。
var Member = function(name)
{
this.name = name;
};
Member.prototype.getName = function()
{
//thisは生成されたインスタンスを指す
console.log(this);
return this.name;
}
var mem = new Member("KN");
console.log(mem.getName()); //KN
7について(イベントリスナー)
JavaScript本格入門 6.7.4(p.352~p.354)に詳しい説明があるので、省略。
8について(静的メソッド)
8は知識として知っておくくらいで良いでしょう:
var Member = function(arg1) { this.p1 = arg1};
Member.method = function() { return this;}
//関数オブジェクト(この場合はMemberオブジェクト自体)を指す
console.log(Member.method())
こうしてまとめてみると、ややこしいですな。
補足
-
use strict
でのthisの挙動は別のページ(http://qiita.com/knknkn1162/items/495b2b767f48d83a6ea5#_reference-6c03e3921ec4d3073979) にまとめました。
-
最初は挙動がわからなかったのですが、http://stackoverflow.com/questions/22770299/meaning-of-this-in-node-js-modules-and-functions を皮切りに解決できました。 ↩
-
「まとめ」とあるけど、まとめるのめんどくさかったんです;( ↩
-
逆に戻り値returnのついた関数に関して、コンストラクタとして呼び出す、つまりnew演算子を用いた場合は、かなり複雑な挙動になるようです。https://teratail.com/questions/23939 が参考になると思います。 ↩
-
JavaScript本格入門 5.1.2 p.225 Note)参照。上の規則からアロー関数中のthisはアロー関数自体が属するオブジェクト(下記の例では空のオブジェクト)になるので、thisが新しく生成されるインスタンスを指すことを明示するnew演算子とバッティングしてしまうからではないかというのが理由だと思います。 ↩