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
を保持します。