JavaScript の this は、(他のプログラム言語から見ると) ちょっと面白い挙動に見えることがあります。
先日、この this の挙動について、会社の同僚が説明してくれたのですが、これまで聞いた説明の中で一番分かりやすいと感じたので、頑張って日本語で説明してみます。
分かりにくかったら、多分それは私の技量不足。
this と function の関係
function が基準スコープになるのがまず一点。
その function をどう呼ぶかで変わるのかがもう一点。
それを踏まえて……
this は function を呼んだ時の . の前についているオブジェクトを指している
と理解できるというのが、同僚の説明でした。
. が省略された場合はグローバルオブジェクトになります (non-strict モード時)。 strict モードでは undefined になります。(@ryo511 さん、ご指摘感謝)
サンプルコード
これだけだとナンノコッチャかもしれないので、具体例を……
function test() {
console.log(this)
}
this の内容をコンソールに表示するだけの、なんてことない関数です。
これをブラウザ上で呼び出すと……
test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
のようになります。
呼び出し時に . がないので、 this はグローバルオブジェクト、ブラウザでは Window オブジェクトになります (non-strict モード)。
この関数 test を特定のオブジェクトに結びつけます。
function test() {
console.log(this)
}
var obj = {}
obj.test = test
その上で、 obj.test() を呼び出すと、 this は obj になります。
obj.test() // => {test: ƒ}
関数 test を呼び出す際、 . がついていて、 obj. となっているので、関数 test の中では this = obj となるという訳です。
逆に、あるオブジェクトに結びついているものをグローバルスコープで呼び出すこともできます。
var obj = { test: function() { console.log(this) } }
var test = obj.test
test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
test() は obj の関数 test を呼び出していることになりますが、呼び出し時に . がついていないので、グローバルオブジェクトが this になります (non-strict モード)。
上のサンプルコードでは、分かりやすくしていますが、例えば、setTimeout などで obj.test をコールバックとして渡すような場合でも同様のことが起きています。
メソッドチェーン
複数の function を数珠つなぎで呼び出すメソッドチェーンは . の前が関数になりますが、この場合、その関数が返すオブジェクトを参照することになります。
関数で return を省略したり、 return 単独で呼ぶと undefined が返ってくるので、メソッドチェーンは利用できません。
var obj = {
test: function() { return this },
alert: function(msg) { console.log(msg) }
}
var test = obj.test
obj.test().alert("hello") // => hello とコンソールに表示
test().alert("hello") // => アラート表示
関数 alert の呼び出しに . があって、その前に関数 test があります。
関数 test の返り値は this なので、その test の呼び出し方によって参照されるオブジェクトによって関数 alert の結果が変わります。
call と apply
call や apply を使って関数を呼び出すと . に前につけるオブジェクトを指定することができます。
function test() {
console.log(this)
}
var obj = { name: "obj" }
test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
test.call(obj) // => {name: "obj"}
test.call(obj) は obj.test() に等しくなります (ただし、 obj に test というメソッドは追加されません)。
コンストラクタ
function に対して、 new 構文を利用して、オブジェクトを作成することができます。
これは大雑把に言ってしまえば、 call の変形構文と考えることができます。
例えば、以下のコード……
var obj = new function() {
this.name = "obj"
console.log(this) // => {name: "obj"}
}
この時、 function が呼ばれますが、 this はグローバルオブジェクトでも undefined でもありません。
new を使うと、新規にオブジェクトを作成して、それに対して、 call を使って function を呼び出して、関数内部で return this するような動作になります。
つまり……
var obj = function() {
this.name = "obj"
console.log(this) // => {name: "obj"}
return this
}.call({})
と同じ動作と考えることができます。
これは無名関数の場合ですが、名前付き関数 (例えば function Test()) の場合、内部でもう少し追加処理が入ります。 ただし、 this の挙動に関しては、基本的に同様に考えることができると思います。
bind
bind はこの . ルールの挙動を変化させて、強制的にあるオブジェクトと結びつけます。
function test() {
console.log(this)
}
var obj = { name: "obj" }
var check = test.bind(obj)
check() // => {name: "obj"}
関数 check の呼び出しには . がついていませんが、 bind されているので、呼び出し時に obj. が付く形となり、関数 test の中で this = obj になります。
関数の中の関数
. はあくまでも関数呼び出し時に参照されるものです。
var obj = {
test: function() {
console.log(this) // *1
function test() {
console.log(this) // *2
}
test()
}
}
obj.test()
のようなコードがあった時、
*1 では obj がコンソールに表示され、 *2 ではグローバルオブジェクト (例えば、 Window) が表示されます (non-strict モード時)。
*2 の関数呼び出しに . がないからですね。
var obj = {
test: function() {
console.log(this) // *1
function test() {
console.log(this) // *2
}
test.call(this)
}
}
obj.test()
と call を使えば (apply でもいいです)、どっちも obj になります。
まとめ
this は function を呼んだ時の . の前についているオブジェクトを指している
- 関数呼び出しの
.の前のオブジェクトがthisになる -
.を省略するとグローバルオブジェクトを参照する (non-strict モード)- strict モードでは
undefinedになる
- strict モードでは
-
call,applyを使うと.の前のオブジェクトを指定できる (コンストラクタはこの変形型) -
bindを使うと.の前のオブジェクトを強制できる
(補足)アロー関数について
当記事では、あえて ES2015 から導入されたアロー関数について触れていませんでした。
アロー関数での this の振る舞いは (他のプログラム言語から見て) 比較的自然で分かりやすい気がします。
最初に触れたとおり
-
functionが基準スコープになる
のが JavaScript の大きな特徴です (var のスコープもこれに準じます)。
そのため、function を入れ子にした途端 this の指すものが変わってきてしまうのが一番の混乱の要因です。当記事では、そこに焦点を当てて説明しています。
アロー関数は入れ子にしても、アロー関数の中で参照されている this が指すものは変わりません。そのアロー関数がキャプチャされたときの this を保持します。
