で、オブジェクト指向の基本的な考え方に、「内部変数はそのオブジェクトが持つメソッドからのみアクセスする」ってのがあります。a の内部変数 x にアクセスするには、a のメソッドのどれか(ここでは m しかないけど)の中から this.x とするしかない、ってことです。これをカプセル化と呼びます。カプセル化は不便に見えるかも知れないけど実は便利な機能です。
var a = { x: 0 };
function f() { this.x++; }
a.m = f;
こうすると関数 f が、オブジェクト a のメソッド m として呼び出せるようになります。ここで、
a.m();
とすれば、a が持つ内部変数(属性、property)x の値が1増えます。
この時の a と f の関係を描くと下の図のようになっています。
ここで気づくのは、(関数である)オブジェクト f は、他のオブジェクト a の内部変数をいじる!ってことです。オブジェクト指向はカプセル化だよね〜とか思っている私のように古いタイプ(?)の人間は、this.x と書かれたらまさか他のオブジェクトの内部変数をいじってるとは思わないので、これを理解するというか納得するのに時間がかかりました。(いや、分かってしまえば、「関数がメソッドになる」「関数は独立したオブジェクトだ」とはそういう意味なんだけれども…)
というわけで私と同類な人向けに:JavaScript の this はカプセル化じゃありませんよ!
じゃあ this の意味は何か。同じ関数 f を別のオブジェクト b のメソッドにもしてみると分かりやすい。
var b = { x: 0, n: f };
これで関数 f は、a.m() としても呼べるし、b.n() としても呼べるようになりました。f のコード中にある this.x は、a.m() として呼ばれた時には a の中の x を意味し、b.n() として呼ばれた時には b の中の x を意味します。
と言うか、そういう意味になるように、this が指すものを、関数の呼び出し方によって変えているのです。a.m() として呼ばれたコードの実行中には this は a を指し、b.n() として呼ばれたコードの実行中には this は b を指す。
再び同類向けに:this は「このオブジェクト」ではなく、「この関数が、あるオブジェクト a のメソッド m として与えられており、なおかつ a.m() の形で呼ばれた場合、a」という意味。長い。てか全然 this じゃない。
(この関数をメソッドとして呼んだオブジェクト、と言いたいところだけど、そうでもない。a.m() を呼ぶのは a ではなくて、そのコードが書かれた何か(関数とかグローバルなコードとか)なので…)
だから、こんなコード
var o1 = {
x: 0,
m: function() {
this.x++;
}
};
を書いて、「これでこの関数が呼ばれる時には必ず o1 の属性 x が増える」と安心してはいけないのです。たしかに o1.m() で呼ばれるとそうなる。でもここで、
var o2 = {
x: 0,
m: o1.m
};
o2.m();
とすれば、同じく o1 の「中に書いてある」(ように見える)関数が実行されるけど、増えるのは o2 の方の x。この時の状況はこう↓(上の a と b の場合と同様)
(a.m = a.m) の右辺の式の値として、生の f が出てくる。この値(関数そのもの)を使って呼んでるからメソッドとして呼んでない。よって1と同じ。
メソッドがオブジェクトに所属する普通のオブジェクト指向とは違って、JavaScript では関数が自由にどんなオブジェクトのメソッドにもなれる(a.m = f として a.m() と呼ぶ)。でもそれが自由なら別にわざわざ a.m = f として属性にすることなく a のメソッドとして呼べてもいい(f.apply(a))。
新しくできたオブジェクトを o とし、そのオブジェクトが初期化関数を表す属性 init を持ちそれが F を指しているとして、o.init のように呼ばれたと考えれば、普通のオブジェクト指向言語と同じ。
とは言え、
var g = function() { this.x++; };
g.x = 0;
として
g();
が自分(関数 g)の x を増やさないのは何とも…(ってまだ言ってる。それは this じゃないんだって。)
まとめ。
this は「このオブジェクト」と思わない方がいい。
this は「メソッドとして呼ぶ呼び方でこの関数が呼ばれた時の、指定されたオブジェクト」を意味する。