Go to list of users who liked
More than 5 years have passed since last update.
JavaScript の this を理解する多分一番分かりやすい説明
JavaScript のthis
は、(他のプログラム言語から見ると) ちょっと面白い挙動に見えることがあります。
先日、このthis
の挙動について、会社の同僚が説明してくれたのですが、これまで聞いた説明の中で一番分かりやすいと感じたので、頑張って日本語で説明してみます。
分かりにくかったら、多分それは私の技量不足。
this
とfunction
の関係
function
が基準スコープになるのがまず一点。
そのfunction
をどう呼ぶかで変わるのかがもう一点。
それを踏まえて……
this
はfunction
を呼んだ時の.
の前についているオブジェクトを指している
と理解できるというのが、同僚の説明でした。
.
が省略された場合はグローバルオブジェクトになります (non-strict モード時)。 strict モードではundefined
になります。(@ryo511 さん、ご指摘感謝)
サンプルコード
これだけだとナンノコッチャかもしれないので、具体例を……
functiontest(){console.log(this)}
this
の内容をコンソールに表示するだけの、なんてことない関数です。
これをブラウザ上で呼び出すと……
test()// => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
のようになります。
呼び出し時に.
がないので、this
はグローバルオブジェクト、ブラウザではWindow
オブジェクトになります (non-strict モード)。
この関数test
を特定のオブジェクトに結びつけます。
functiontest(){console.log(this)}varobj={}obj.test=test
その上で、obj.test()
を呼び出すと、this
はobj
になります。
obj.test()// => {test: ƒ}
関数test
を呼び出す際、.
がついていて、obj.
となっているので、関数test
の中ではthis
=obj
となるという訳です。
逆に、あるオブジェクトに結びついているものをグローバルスコープで呼び出すこともできます。
varobj={test:function(){console.log(this)}}vartest=obj.testtest()// => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
test()
はobj
の関数test
を呼び出していることになりますが、呼び出し時に.
がついていないので、グローバルオブジェクトがthis
になります (non-strict モード)。
上のサンプルコードでは、分かりやすくしていますが、例えば、setTimeout
などでobj.test
をコールバックとして渡すような場合でも同様のことが起きています。
メソッドチェーン
複数のfunction
を数珠つなぎで呼び出すメソッドチェーンは.
の前が関数になりますが、この場合、その関数が返すオブジェクトを参照することになります。
関数でreturn
を省略したり、return
単独で呼ぶとundefined
が返ってくるので、メソッドチェーンは利用できません。
varobj={test:function(){returnthis},alert:function(msg){console.log(msg)}}vartest=obj.testobj.test().alert("hello")// => hello とコンソールに表示test().alert("hello")// => アラート表示
関数alert
の呼び出しに.
があって、その前に関数test
があります。
関数test
の返り値はthis
なので、そのtest
の呼び出し方によって参照されるオブジェクトによって関数alert
の結果が変わります。
call
とapply
call
やapply
を使って関数を呼び出すと.
に前につけるオブジェクトを指定することができます。
functiontest(){console.log(this)}varobj={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
の変形構文と考えることができます。
例えば、以下のコード……
varobj=newfunction(){this.name="obj"console.log(this)// => {name: "obj"}}
この時、function
が呼ばれますが、 this
はグローバルオブジェクトでもundefined
でもありません。
new
を使うと、新規にオブジェクトを作成して、それに対して、call
を使ってfunction
を呼び出して、関数内部でreturn this
するような動作になります。
つまり……
varobj=function(){this.name="obj"console.log(this)// => {name: "obj"}returnthis}.call({})
と同じ動作と考えることができます。
これは無名関数の場合ですが、名前付き関数 (例えばfunction Test()
) の場合、内部でもう少し追加処理が入ります。 ただし、this
の挙動に関しては、基本的に同様に考えることができると思います。
bind
bind
はこの.
ルールの挙動を変化させて、強制的にあるオブジェクトと結びつけます。
functiontest(){console.log(this)}varobj={name:"obj"}varcheck=test.bind(obj)check()// => {name: "obj"}
関数check
の呼び出しには.
がついていませんが、bind
されているので、呼び出し時にobj.
が付く形となり、関数test
の中でthis
=obj
になります。
関数の中の関数
.
はあくまでも関数呼び出し時に参照されるものです。
varobj={test:function(){console.log(this)// *1functiontest(){console.log(this)// *2}test()}}obj.test()
のようなコードがあった時、
*1
ではobj
がコンソールに表示され、*2
ではグローバルオブジェクト (例えば、Window
) が表示されます (non-strict モード時)。
*2
の関数呼び出しに.
がないからですね。
varobj={test:function(){console.log(this)// *1functiontest(){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
を保持します。
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme