javascriptでthisと戯れる
最近のありえるはGoやったり、Closureやったりかっこ良いですね。
このあたりは社内の自称二十代の勉強会で教えて貰えることを期待しつつ、
新人さん向けに開催したjavascript勉強会の小ネタとして、
javascriptのthisについての話をしたのでその内容を載せてみます。
javascriptが本業の方などからしてみれば自明な内容かもしれませんが、
java等から来た人間からすると、わりと嵌りがちな部分です。
まずはグローバルスコープでのthisについて。
グローバルスコープでのthisはグローバルオブジェクトとよばれるオブジェクトを参照しています。
print(this); // [object global] thisはグローバルオブジェクトを参照
グローバルオブジェクトとはグローバル変数を格納するトップレベルのスコープオブジェクト。
IEなんかで言うところのwindowオブジェクトがそれにあたります。
次にメソッド内でのthisについて。
obj.func()の形式で呼び出されたメソッドの内部ではthisはobjを参照しています。
var obj = {
func: function() {
print(this); // [object Object]
print(this.func != undefined) // true
}
};
obj.func();
javaと違ってjavascriptでは自身のプロパティを参照するのにthisは省略できません。
省略すると意味が変ってしまい、現在のスコープオブジェクトへの参照になってしまいます。
var obj = {
func2: function() {
print(this); // [object Object]
print(this.func1 != undefined) // true
},
func1: function() {
this.func2(); // thisの省略は不可
}
};
obj.func2();
関数内で定義したローカルな関数を呼び出すとその内部ではthisの値が変ってしまいます。
これについては後述します。
ローカル関数内で宣言元のプロパティを参照する方法の一つとして、
ローカル関数外でthisを適当な変数に代入した後、ローカル関数内でその変数を参照する方法があります。
var obj = {
func2: function() {
print(this); // [object Object]
},
func1: function() {
var self = this; // selfにthisの値(obj)を代入
var local = function() {
self.func2(); // self(obj)のfunc2を呼び出し
};
local();
}
};
obj.func1();
このあたりからjavaやC++のthisと挙動が違ってきましたね。
javascriptの場合、thisの値はどのように決まるのでしょうか?
答えはFunction.applyの第一引数によって決まります。
var obj = {
func: function() {
print(this);
}
};
// 以下の二行は等価
obj.func(); // [object Object]
obj.func.apply(obj); // [object Object]
Function.applyは第一引数にthisの参照先を、
第二引数に関数の引数の配列(今回はいらないので省略)を受けとる関数です。
そしてobj.func()は言わばobj.func.apply(obj)のシンタックスシュガーと言えます。
応用するとこんなこともできます。
var obj = {
func: function() {
print(this); // [object global]
}
};
obj.func.apply(this); // applyの第一引数にグローバルオブジェクトを指定
obj.func.applyの第一引数にグローバルオブジェクトを指定しており、
その結果objのfunc内のthisはグローバルオブジェクトになります。
ではメソッドとしてではなく、関数として呼び出された場合はどうなるのでしょうか?
var func = function() {
print(this); // [object global]
};
func();
グローバルスコープに定義されたfuncを呼び出した場合のthisはグローバルスコープ。
ここはなんとなくOKですね。
var obj = {
func: function() {
var local = function() {
print(this); // [object Object]
};
local.apply(this); // this == obj
}
};
obj.func();
func内で定義されたlocalのapplyにthis(obj)を指定すると、local内のthisはobj。
これもまぁOKです。
var obj = {
func: function() {
var local = function() {
print(this); // [object global]
};
local();
}
};
obj.func();
え、グローバルオブジェクト?
thisはてっきりobj.funcのCallオブジェクトかと思いましたがグローバルオブジェクトを指していました。
この動作は腑に落ちなかったのでオライリーのサイ本を読んでみると、以下の様な記述を発見。
■ O'REILLY JAPAN JavaScript 第五版 p134
関数がメソッドではなく関数として呼び出された場合、
thisキーワードはグローバルオブジェクトを参照します。
ややこしいことに、メソッドとして呼び出された関数内に入れ子にされた別の関数が
関数として呼び出された場合も、グローバルオブジェクトを参照します。
どうも仕様みたいです。
実験。
// globalFuncの宣言。
// グローバルスコープで宣言されたプロパティはグローバルオブジェクトが保持する。
var globalFunc = function() {
print(this);
};
// グローバルスコープでのthisはグローバルオブジェクトを差す。
// その為以下の二行は等価
globalFunc(); // [object global]
this.globalFunc(); // [object global]
var obj = {
func: function() {
var localFunc = function() {
print(this);
};
(function() {
// ここから先のthisはグローバルオブジェクト
globalFunc(); // [object global] スコープチェインを辿るので参照可能
localFunc(); // [object global] 同様
this.globalFunc(); // [object global] グローバルオブジェクトにglobalFuncは存在する
this.localFunc(); // エラー グローバルオブジェクトにlocalFuncは存在しない
})();
}
};
obj.func();
そしてこの動作はapplyの第一引数を省略した場合も同様です。
var obj = {
func: function() {
var local = function() {
print(this); // [object global]
};
local.apply(); // applyの第一引数を省略
}
};
obj.func();
local()がlocal.apply()のシンタックスシュガーってことか。
まとめるとこんな感じ。
- グローバルスコープでのthisはグローバルオブジェクト
- 関数内でのthisの参照先はFunction.applyの第一引数で、省略時はグローバルオブジェクト
- obj.func() と obj.func.apply(obj) は等価
- func() と func.apply() はそれぞれ等価
試した処理系はrhino 1.7 release 1。
一部の動作は処理系に依存してるかもしれません。今度試してみます。
今回はスコープやプロトタイプについての話はしてませんし、
コンストラクタでのthisの話も忘れてました。気が狂ったらその内追記します。かも。
あわせて読みたい
■JavaScriptのthisキーワードをちゃんと理解する - builder by ZDNet Japanhttp://builder.japan.zdnet.com/sp/javascript-kickstart-2007/story/0,3800083428,20371112,00.htm
■JavaScriptのオブジェクト、コンストラクタ、プロトタイプ、スコープ等 - qnzm.log(クニジマログ)
http://d.hatena.ne.jp/qnzm/20081014/1223934778
■JavaScript の this について - IT戦記
http://d.hatena.ne.jp/amachang/20070917/1190015123
おしまい。
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/iwanaga/js-this/tbping
Re:javascriptのthisについて
(以下、globalオブジェクトをわかりやすいようにwindowで代表させています)。
- x.foo()の呼び出しで、常に暗黙に this = x の代入が起きる
- apply(y)の呼び出しで、常に暗黙に this = y の代入が起きる
- x.が省略されると、常に window. があると見なされる(with文の内部では異なるので、統一的に見るなら、Webブラウザでは暗黙に with (window) {} が外側にあるイメージ)
- new(演算子だっけ?)の時、暗黙にthisに生成オブジェクトの代入が起きる