描き尽くされたネタだとは思いますが、この記事ではJavaScriptのthisの参照先の違い3つと、それを理解する上で必要なJavaScriptの前提知識4つをまとめたのでアウトプットします。
★ Special thanks ★
JavaScript入門書-関数とthis-
The JavaScript this Keyword
What is this
value in JavaScript?
thisの参照先には3つの違いがある
- 実行コンテキストとthis
- 関数とメソッドにおけるthis
- コンストラクタにおけるthis
thisの判断には大きく3つあります。各項目ごとにポイントがあるのでそれぞれの項目ごとにthisの参照先の違いを明らかにしていこうと思います。がその前に、thisの参照先を理解するためには、まずはしっかりと【結論】を頭に入れて置かないといけません。
【結論】thisには約束がない
こちらのサイトの一番最後の行にもありますが、JavaScriptにおいては、環境やコンテキストによってthisの参照先が変化します。そのため、オブジェクトと紐づいていない関数つまりメソッド内以外での関数において、thisを使うことは避けましょう。thisを使ってグローバルオブジェクトを呼び出すといった行為は避けましょう。
結論を頭に入れたところで、「それはなぜ?」を理解するため、さっそくthisの参照先について確認していきます。上述した3つのthisを学ぶ上で必要なJavaScriptの基礎知識をこの記事後半の『JavaScriptはどんな言語?4つの前提知識』にまとめましたので参考にしていただければ思います。が、参考にしたのは私です。
- (※1基礎知識)大部分がオブジェクト
- (※2基礎知識)2種類の実行コンテキスト
- (※3基礎知識)関数とメソッド
- (※4基礎知識)コンストラクタ
実行コンテキストとthis
実行コンテキストには2つあり、この2つでthisの参照先が変わってきます。
2つのコンテキストの違いについての基礎知識は(※2基礎知識)を参照。コンテキストってなんだっけ?という方はそちらを見ていただけると参考になるかもしれないです。
- Scriptモードにおけるthis
<script>
console.log(this);
</script>
デベロッパーツールでConsoleを確認すると以下のように表示されました。
こうして、Scriptモードにおけるthisの参照先はwindowオブジェクト(グローバルオブジェクト)ということがわかりました。では、次にModuleモードのthisを確認してみます。
- Moduleモードにおけるthis
実行コンテキストは、 type="module"
と設定しない限りScriptモードになっているので注意します。
<script type="module">
console.log(this);
</script>
同じくConsoleを確認すると、undifined となってしまいました。
Scriptモードにおけるthisの参照先はwindowオブジェクト(グローバルオブジェクト)でしたが、Moduleモードではundifinedとなり参照先が違っていました。では、Moduleモードではグローバルオブジェクトを参照できないのでしょうか?
もしも、Moduleモードにおいて、グローバルオブジェクトを参照したい場合は
<script type="module">
console.log(window);
</script>
と書くことで直接グローバルオブジェクトを参照できます。が!!!!
(くどいですが大事なことなのでもう一度)
グローバルオブジェクトを呼び出したいからといってthis
を書いていたとしましょう。すると、Moduleモードではundefinedになってしまいます。このように、thisを記述したが意図した挙動にならない、そういう事態が起こってしまうようなコードを書くのは避けなければいけません。2つのコンテキストにおけるthisを学んだだけでもグローバルオブジェクト呼び出しのために、thisと書いてしまうのは好ましくないことがわかりました。
では、次のthisへ進みましょう。
関数とメソッドにおけるthis
この条件におけるthisを考える時のポイントは、関数の種類を意識することです。
関数の種類とは、Arrow functionかそうでないかを指しておりそのどちらなのか判断することでthisの参照先の違いを確認できます。
関数と宣言についての基礎知識は(※3基礎知識)を参照。関数とメソッドの違いってなんだっけ?という方はそちらを見ていただけると参考になるかもしれないです。
- Arrow functionでない方の
this
thisは、ベースオブジェクトで判断します。
ベースオブジェクトとは、メソット呼び出しの際の、.(ドット)やブラケット([])のすぐ左にあるものを指します。
例えば
object.method();// 1
object.user.method(); // 2
1のmethod()から見たベースオブジェクトはobject
2のmethod()から見たベースオブジェクトはuser
を指します。
つまり、関数はメソッドとは違いベースオブジェクトはないのです。thisは、呼び出し方で参照先に違いが出ることがわかりました。
const user = {
fullName = "Sakiyama Hogeko",
sayName() {
return this.fullName;// ...(1)
};
};
メソッドとは、オブジェクトのプロパティが関数である場合を言っています。
オブジェクト(user)内の関数の呼び出し方は、
user.sayName();
となります。
メソット呼び出しの際の、.(ドット)やブラケット([])のすぐ左にあるものはベースオブジェクトなので、
sayName() {
return user.fullName;
};
と同義であり、...(1)のthisが指すものはuserとなります。
これを使って、メソッドの中から同じオブジェクト内のプロパティをthisで呼び出すことができます。(幾重にネストしてもthisで参照できるものは同じオブジェクト内のプロパティということ)
- Arrow functionにおける
this
関数がArrow functionのとき、thisは関数が定義された時に決まります。
例えば、Arrow function内で以下のようにthisを使用したとします。
"use strict";
function outer() { // Arrow functionでない関数の定義方法
return fn = () => { // Arrow functionの定義方法
return this;
};
};
Arrow functionで定義された関数内でのthisは、一番近くのthisが呼ばれます。上記のときthisは、1つ外側のouter()のthisを見にいくことになります。
"use strict"; においては実行コンテキストにおけるthisで確認しましたが、結果はundefinedとなります。
Arrow functionとそうでない関数のthisの違いを確認しわかることは、Arrow functionでのthisはそもそもundefinedになり、また、Arrow functionでない関数内で呼び出したthisはベースオブジェクトで判断するというようなわかりづらいことが確認できました。そのため結論で述べたように、メソッドでない通常の関数の中でthisを呼び出した場合、何を呼ぶかわかりづらいためthisを使うのは避けた方が良いとわかります。
では、次のthisへ進みましょう。
コンストラクタにおけるthis
コンストラクタ内でのthisは、これから新しく作るインスタンスオブジェクトになります。
コンストラクタについての基礎知識は(※4基礎知識)を参照。コンストラクタってなんだっけ?という方はそちらを見ていただけると参考になるかもしれないです。
例えば、以下のようなコンストラクタ
class クラス {
メソッド() {
this //(メソッド内でのthisの参照先はベースオブジェクト)
}
}
クラスのメソッドを呼び出そうとすると、
const インスタンス = new クラス();
インスタンス.メソッド();
メソット呼び出しの際の、.(ドット)やブラケット([])のすぐ左にあるものがベースオブジェクトでした。つまり、インスタンスがベースオブジェクトに当たります。よって、コンストラクタ内でのthisは、これから新しく作るインスタンスオブジェクトになるというのが理解できます。
これにて、JavaScriptのthisの参照先の違い3つについてのまとめは終了です。
これまで思いがけない挙動を生んでしまっていた原因がJavaScriptにおけるthisの参照先であったことは多々ありました。今回thisの参照先3つの違いを学ぶことで、JavaScriptの理解がより深まり、プログラマとして若干階段を登れた気がします。
以下、『JavaScriptはどんな言語?4つの前提知識』
JavaScriptはどんな言語?4つの前提知識
(※1基礎知識)大部分がオブジェクト
JavaScriptはそれぞれのオブジェクトがが連携して動いている。
ECMAScript : どの環境でも動作する仕様
実行環境の仕様 : 環境ごとに定義された機能
ユーザが定義したオブジェクト
実行環境とはブラウザ、サーバ側、デバイスの3つを指していて、ブラウザ環境ではUesrInterFace(UI)を操作するJavaScriptの機能があり、サーバ側はNode.jsのようなサーバ側の処理をする機能、デバイス環境にはデスクトップアプリやスマホアプリを動かす機能というように、それぞれの環境で動くJavaScriptが連携している。
以上の3つにオブジェクトらで構成されています。
(※2基礎知識)2種類の実行コンテキスト
実行コンテキストは Module と Script の2つに分けられる。この二つの違いを意識する必要はないようですが、Module型を使ってJavaScriptを記述するという設定を記述しない限りはScriptモードになっている。それぐらいの理解でいいでしょう。
Moduleは、ECMAScript2015で導入された、strict mode(厳格な実行モード)でコードを書くことができる仕様のようです。strict modeのおかげで、曖昧な定義のものは使えなくなるなど、私たちが安全にコードを書くことができるようになったのです。(知らんけど⭐︎)
Moduleの恩恵を受けるようにするには、 "use strict;"
という文字列をファイルもしくは、関数の先頭に記述することで、そのスコープ(影響下)にあるコードはstrict modeで書けるようになります。
例えば、どういうものが書くことが出来て、書くことが出来ないかというと
(ダメな記述) myAge = 25;
(良い記述例)var myAge = 25;
Scriptモード
<script>
</script>
type="module"
が設定されていないコンテキストがScriptとなります。
(※3基礎知識)関数とメソッド
関数とは、関数単体のものを指し、メソッドとはオブジェクトと紐づいているものをメソッドといいます。
// 関数
function() {
return hoge;
}
// オブジェクト.メソッド
$store.state.function()
オブジェクトとクラスにおけるメソッドの定義方法の違いに注意
- オブジェクト内でのメソッドの定義方法
const object = {
// key : value
method1: function() {
}
};
- クラス内でのメソッドの定義方法
class クラス {
メソッド() {
hogehoge
}
}
(※4基礎知識)コンストラクタ
プロパティは、stateを持つこともできる、部長クラスに持たせた電話ステータスがその例です。
電話中 : true or false のstateがある
つまり「部長は電話中です」はプロパティ