JavaScript 『 this 』キーワードまたはpromise/async/await・スコープ・巻き上げなどを学習し始めたあなたは、すでにJavaScript中級者のレベルに到達しています。
これは技術的な中級者ではなく、概念の理解をするための難易度だと思って下さい。
ですが、もちろん技術的な中級者になるための1ステップでもあります。
Promiseの概念の理解もthisと同じぐらい難易度は高いですが、人によっては難易度が急に跳ね上がったと感じる方々もいます。
それではthisの学習を始めましょう。
thisとはなんですか?
thisキーワードは、間違いなく理解するのが最も難しいJavaScriptの概念の1つです。
ただし、これはJavaScriptコードの非常に一般的な部分であるため、優れた開発者になるための鍵となることを理解してください。
thisそれが属するオブジェクトを指します。
また、実行コンテキストのプロパティとして定義することもできます。
実行コンテキストとは、コードを実行する際の環境です。
ですので、JavaScriptでは、thisは現在コードを実行しているオブジェクトを参照します。
グローバルコンテキストはグローバルスコープです。
関数コンテキストは関数スコープです。
例えば『関数がどのように呼び出すか』です。
つまり実行コンテキストとは、現在の実行環境の状況となります。
thisオブジェクトは、実行コンテキストが変更されるたびに、その変更を参照します。
厳格モードでない場合は常にオブジェクトへの参照となっています。
thisは、主にオブジェクト指向プログラミングのコンテキストで使用されることに注意してください。
そのことを念頭に置いて、thisはそれが属するオブジェクトを参照します。
どのオブジェクトのthisが使用または呼び出されるかによって異なり、関数は呼び出されたときにバインドされます。
thisは通常、何かにアクセスしたいときに使います。
例えば、独自のオブジェクトを持っていて、あるメソッドの中にあるプロパティを使いたい場合、thisを使う必要があります。
つまり、thisはメソッドやプロパティを参照するために使用し、純粋関数および変数を参照する場合は省略します。
function changeDrink(){ //ここでは参照する意味はありません。 this.drink = "coffee"; } const myDrink = { drink: "coke" }; myDrink.changeDrink = changeDrink; //ここでは、thisが呼び出されたmyDrinkを参照しているので意味があります。 console.log(this); console.log(myDrink);
詳しくはこれから解説していきます。
何がthisを指しているのかを知るには、関連する関数がどのように呼び出されるかを知る必要があります。
それらを学んでいきましょう。
グローバル実行環境
this変数を理解し始めるために、まず抑えておかなければいけないことは、アプリケーションの実行環境の参照となります。
ブラウザでJavaScriptを実行した場合、その環境はオブジェクト自体によって表されるwindowのため、デフォルトのthisはobject Windowとなります。
console.log(this === window); // true
では、Node.jsのglobalを使用した実行環境の場合では、変数で表されるため下記のようになります。
console.log(this === global); // false
ブラウザの場合、グローバルオブジェクトはウィンドウオブジェクトです。
Node.js環境では、例えば特別なオブジェクトであるglobalはthisの値となります。
オブジェクト指向プログラミングにおけるthisの同じ変数が、オブジェクト自身を表すという概念よりも、実行環境や実行コンテキストとの関連性がはるかに高いという事になります。
thisは、使用方法に応じてさまざまなオブジェクトを参照します。
また、JavaScriptのスコープ概念にまだ慣れていない場合は、JavaScriptの変数、スコープ、および巻き上げについてを学習してください。
DOMの要素やイベントにアクセスしようとしている場合でも、オブジェクト指向プログラミングスタイルで記述するための、クラスを構築している場合でも、通常のオブジェクトのプロパティとメソッドを使用している場合でも、thisに遭遇します。
単独のグローバルスコープ
厳格モードと非厳格モードで見てみましょう。
thisを単独で使用する場合は、グローバルオブジェクトを指します。
"use strict"; // 厳格モード let x = this; console.log(x); // [object Window] this.name = 'Taro'; console.log(window.name); // Taro
上記での、thisはグローバルスコープで実行されているためwindow.nameとthis.nameは同じです。
ブラウザウィンドウでの、グローバルオブジェクトは[object Window]です。
厳格モードではthis単独で使用すると、グローバルオブジェクトを参照しています。
非厳格モードではどうでしょうか。
let x = this; console.log(x); // [object Window] this.name = 'Taro'; console.log(window.name); // Taro
出力は同じとなります。
this単独での、この動作は厳格モードと非厳格モードの両方で一貫している事になります。
つまり、関数の外部のthis値は常にグローバルオブジェクトです。
※ 厳格モードの詳細と、それら間違いやセキュリティに関して、どのような変更が行われるかについては、MDNの厳格モード Strict mode - JavaScript | MDN - JPのドキュメントを参照してください。
Strict mode - JavaScript | MDN - US
関数内部のthis
Javascriptでは関数内のthis変数にアクセスできます。
原則として、これらの場合の値は、関数を呼び出したオブジェクトに関連付けられます。
function sample() { // thisはグローバルオブジェクトを指す console.log(this); } sample(); // output: [object Window]
非厳格モードでの関数内部で使用される場合のthis変数を出力することで得られるのはウィンドウオブジェクト(ブラウザのオブジェクト)となります。
なぜなのか?
関数を呼び出すオブジェクトである、sample()はデフォルトではグローバルのウィンドウオブジェクトであるためです。
どこで定義したかではなく、どこで呼び出したかが重要です。
下記は別の例です。
function sample() { console.log(this === window); // true } sample();
では厳格モードの関数内部ではどうでしょうか
厳格モードを特定の関数にのみ適用する場合は、関数本体の上部に配置します。
特定しない場合は関数外、ファイルの最上部に配置してください。
function sample() { "use strict"; console.log(this); // undefined } sample();
関数内部で厳格モードでのthisはundefinedです。
ではチェックしてみましょう。
function sample() { "use strict"; console.log(this === undefined); // true } sample();
出力がtrueなので、厳格ではthisの参照はundefinedとなっている事がわかります。
関数で厳格モードが有効になっている場合は、オブジェクトの代わりにグローバルオブジェクトが参照されるためthisの値はundefinedを参照します。
注意点は、関数内部で厳格モードを有効にした場合、ネストされた関数の両方に適用されますのでご注意下さい。
コンストラクターでの呼び出し
newキーワードを使用し、関数オブジェクトのインスタンスを作成する際は、関数をコンストラクターとして使用します。
関数がnewキーワードで呼び出されると、その関数はコンストラクター関数と呼ばれ、新しいインスタンスを返します。
このような場合のthisの値は、新しく作成されたインスタンスを参照します。
下記では、Person関数を宣言した後に、コンストラクターとして呼び出します。
function Person(x, y) { this.first_name = x; this.last_name = y; this.userName = function() { console.log(`Name: ${this.first_name} ${this.last_name}`); } } let person = new Person("Taro", "Yamada"); person.userName(); // Name: Taro Yamada let person2 = new Person("Hanako", "Yamada"); person2.userName(); //Hanako Yamada
person.userName(); はthisの新しいインスタンスを指しており、person2.userName();は異なるインスタンスのthisを指しています。
// let person = new Person("Taro", "Yamada"); let person = Person("Taro", "Yamada"); // undefined
newキーワードを省略した場合では、Personのthisの値はグローバルオブジェクトとして設定されており、undefinedが返されます。
クラスでのthis
ES6クラスのメソッド内でthisを使用すると、現在のオブジェクトを指します。
class Person { constructor(name) { this.name = name; } greet() { console.log('Hi! My name is' + `${this.name}`); } } const fistName = new Person('Taro'); fistName.greet(); // Hi! My name is Taro const foo = fistName.greet; foo() //undefined
クラスを扱う場合、関数のメソッドへの参照を保存してもundefinedになることはなく、『Hi! My name is Taro』です。
そして、クラスが厳格モードで動作しているため、thisの値が定義されていない呼び出しでは、thisの値がundefinedとなるためです。
Reactのクラスコンポーネントでは、コンストラクタでイベントに関連するコールバックをバインドしていました。
注意点はすでに知っているように、厳格モードは関数内の動作を変更するため、グローバルオブジェクトを指す代わりに、thisはundefinedとなりますのでご注意下さい。
オブジェクトメソッドでの呼び出し
メソッドとは、オブジェクトに対する関数、またはオブジェクトが実行できるタスクのことです。
メソッドはthisを用いて、オブジェクトのプロパティを参照するために使用します。
オブジェクトのメソッド内で使用されているthisの場合、オブジェクトを参照します。
const person = { name : 'Taro', age: 30, sample() { // thisはオブジェクト自体を指す console.log(this); console.log(this.name); } } person.sample(); /* { name: 'Taro', age: 30, } Taro */
上記では、thisがpersonオブジェクトそのものを参照しています。
See the Pen JavaScript This アロー関数 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
実際に試してみたい方は下記からどうぞ
https://codepen.io/enjinia_f/pen/BarNzrN/?editors=1011
別の例でも見てみましょう。
オブジェクトのメソッドを呼び出します。
let myObj = { greet: 'Hello!!', getGreet: function () { return this.greet; } } console.log(myObj.getGreet()); //output: Hello!!
thisはメソッドを所有しているオブジェクトに設定され、myObjのオブジェクトを参照します。
メソッドは値であるオブジェクトのプロパティであるため、変数に格納が可能です。
let greet = myObj.getGreet;
ではこのまま、変数を介してメソッドを呼び出してみましょう。
console.log(greet());
// undefined
オブジェクトを指定しないで、メソッドを呼び出した際にJavaScriptが非厳格モードと厳格モードでグローバルオブジェクトに設定します。
つまり、変数を関数として呼び出しています。
これを実行すると、メソッドが追跡できなくなったためwindow objectの代わりにundefinedを取得します。
メソッドを別のメソッドへのコールバックとして渡すときに発生します。
これを解決する方法はあります。
thisのbind()
先述のオブジェクトメソッドを変数を介して呼び出しを試みたが、undefinedを返しました。
それをまず解決します。
オブジェクトメソッドのbind()を使用します。
let myObj = { greet: 'Hello!!', getGreet: function () { return this.greet; } } let greet = myObj.getGreet.bind(myObj) console.log( greet() ) //Hello!!
bind()メソッドは、渡された最初の引数を参照して新しいメソッドを作成し返します。
別の例でも見てみましょう。
function sample() { return this.greet; } let x = sample.bind({greet: 'Hello'}); console.log(x()); // Hello
bind()は、既存の関数から新しい関数を返します。
つまり、bind()メソッドが呼ばれると、関数を実行しているのではなく、与えられたthisの値を持った新しい関数を生成しています。
call( )およびapply( )メソッドでのthis
すべての関数にはcall()およびapply()またはbind()メソッドがあります。
関数の実行コンテキストで、カスタム値をthisに設定する事が可能です。
function getPerson(x) { console.log(x + this.person); } let user1 = { person: 'Taro' }; let user2 = { person: 'Hanako' }; getPerson.call(user1, "Name: "); // Name: Taro getPerson.call(user2, "Name: "); // Name: Hanako
上記では、getPerson関数のcall()メソッドを用いて間接的にgetPerson関数を呼び出しています。
call()メソッドの第一引数にuser1とuser2のオブジェクトを渡しているので、それぞれの呼び出しで対応するpersonを取得することができます。
下記はapply()メソッドの場合です
getPerson.apply(user1, ["Name: "]); getPerson.apply(user2, ["Name: "]);
apply()メソッドは、第2引数が配列であることを除けば、call()メソッドと同様です。
唯一の違いが引数の受け渡しである、配列だけです。
call()メソッドは元々関数を持たないオブジェクトを呼び出します。そして、引数は個別に渡されます。
これらメソッドでの注意点は、もしあなたが厳格モード以外で3つのメソッドを使っている場合では、nullおよびundefinedを渡すと、JavaScriptエンジンはそれを無視します。
これは、常に厳格モードでコードを書くことが推奨される理由の一つです。
アロー関数でのthis
ES6の機能として、アロー関数が導入されました。
これは非常に便利な機能です。
ただし、アロー関数は、コンテキストの点で通常の関数とは異なる動作をすることを知っておく必要があります。
JavaScriptでは、関数が実行されるたびに実行コンテキストが作成されます。
しかし『アロー関数』自体は実行コンテキストを定義しないため、すぐ上にあるものから実行コンテキストを取得します。
アロー関数内部でのthisは親スコープを参照します。
const sample = () => { console.log(this); } sample(); // [object Window]
つまり通常の関数とは異なり、アロー関数は独自のthisはありません。
アロー関数でthisを使用する場合は、親スコープオブジェクトを参照します。
const person = { name: 'Taro', getName() {//メソッド let x = () => console.log(this.name); x(); } } person.getName(); // Taro
x()関数内部のthis.nameはpersonオブジェクトを指しています。
また、アロー関数を使用すると、メソッド内部で関数を使用した場合にundefinedが発生する問題を解決できます。
const person = { name : 'Taro', age: 30, city: 'Tokyo', getPerson() { console.log(this); console.log(this.city); let sampleFunc = () => { console.log(this); console.log(this.city) } sampleFunc(); } } person.getPerson();
sampleFunc()はアロー関数を使用し定義しています。
親スコープからthisを取得しているので、this.cityはTokyoと出力されます。
これは、thisとアロー関数を一緒に使用した場合は、外部スコープを参照している事にもなります。
下記のCodepenで出力を確認できます。
See the Pen JavaScript Switch score 範囲 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
実際に試してみたい方は下記からどうぞ https://codepen.io/enjinia_f/pen/bGvdeaV/?editors=1011
それでは、アロー関数でundefinedの例を見てみましょう。
オブジェクトのメソッドとしてアロー関数を使用します。
let person = { name:'Taro', age: 30, getName: () => console.log(this.name + this.age) } person.getName(); // undefined
getNameメソッドでアロー関数を使用します。
上記では、コールバック関数は通常の関数呼び出しなのでアロー関数の親スコープであるグローバルスコープからのものであるため、undefinedを返します。
オブジェクトのメソッドとしてアロー関数を使用することはお勧めしません。
厳格モードで、アロー関数と通常の関数の両方を定義するとします。
"use strict" // 厳格モード console.log(this); //(object window); function myFunc() { console.log(this.name); // Taro let myArrowFunc = () => { console.log(this.name); // Taro } let myNormalFunc = function() { console.log(this.name); // undefined! } myArrowFunc(); myNormalFunc(); } myFunc.call({ name: "Taro" });
https://codepen.io/enjinia_f/pen/bGvdRPw/?editors=1011
上記では、アロー関数の場合でのthisは継承されますが、他の関数の場合、thisはundefinedのままになります。
また、アロー関数のthisはapply()やcall()及びbind()メソッドで変更することはできません。
これは、アロー関数のthisがグローバルスコープである親スコープのものであるためです。
そして、アロー関数は常にthisをバインドしているため、コンストラクターとして使用することはできません。
DOMイベントハンドラー
イベントリスナーのコールバックでthisを使用することで、人によってはthisの概念を理解するのが簡単な場合があります。
ブラウザでは、イベントハンドラのための特別なthisコンテキストがあります。
addEventListenerによって呼び出されたイベントハンドラでは、thisはevent.currentTargetを参照します。
ほとんどの開発者は、DOM内の要素にアクセスするために必要に応じてevent.target またはevent.currentTargetを使用するだけですが、コンテキストでthis参照が変化するので、知っておくことが重要となります。
<button id="hello">Click me</button>
document.getElementById('hello').addEventListener('click', function(e) { console.log(this); });
HTML要素をクリックすると、コンソールログに次のように表示されます。
<button id="hello">Click me</button>
ボタンをクリックすると、ボタンそのものである要素がログに記録されることがわかります。
ご覧の通りですが、thisは対象となる要素、つまりイベントリスナーを追加したHTML要素を参照していることが分かります。
実際にクリックして、ログを確認して下さい。
https://codepen.io/enjinia_f/pen/XWEbZwN/?editors=1011
thisの優先順位
thisキーワードが、どのオブジェクトを指しているかを判断するために4つのルールが適用されます。
優先順位は下記の通りです。
bind()
call()とapply()
Objectメソッド
グローバルスコープ
bind()を使用し、コールバックとして関数が呼び出されているかチェックします。
そうでない場合は、関数がcall()またはapply()メソッドで呼び出されているかどうかを、引数付きでチェックします。
関数がオブジェクトの関数として呼び出されているかをチェックします。
それ以外の場合、関数がグローバルスコープでドット表記なしで呼び出されているかまたは、ウィンドウオブジェクトを使用して呼び出されているかをチェックするようにして下さい。
まとめ
・ thisは、関数内で使用される場合はグローバルオブジェクトを参照しますが、厳格モードの関数で呼び出される場合はundefined(未定義)です。
・単独でのthisはグローバルオブジェクトを参照します。
・ thisはメソッド内で参照されたときに、メソッドを呼び出したオブジェクトを指します。
・ thisは、DOM要素で発生したイベント内で使用される場合にDOM要素を参照します。
・コンストラクターのthisの値は、関数がnew演算子で呼び出されると、新しく作成されたインスタンスを参照します。
・アロー関数内部でのthisは親スコープを参照します。
・ call()やa apply()およびbind()メソッドを使ってnullおよびundefinedを渡すと、JavaScriptはそれを無視します。
最後に
JavaScriptのthisは厳格モードで実行すると、thisの値が変化する事に注意してください。
そしてthisの値は通常では、関数がどのように呼び出されるかによって決定します。
つまり関数が呼び出されるたびに、thisは異なる可能性があるという事です。
ES2015の導入後から、実行コンテキストのいくつかの戦略が変更されましたが、多くは同じままです。
thisを変更してコードの実行コンテキストを明示的に変更する場合は、コードとコメントの両方で、できるだけ明確かつ明示的に記述するようにして下さい。
今は恐ろしくて複雑に見えるかもしれませんが、thisコードで何が参照されているかを知る必要があるときはいつでも、それを見て条件を調べ始めてください。
この知識により、あなたのプログラムにおいてthis値を決定することができるようになるはずです。
JavaScriptコードで、シンプルで非常に重要なthisキーワードをどこでどのように使用するかを理解するのに役立つことを願っています。
本日は以上となります。
最後までこの記事を読んで頂きありがとうございます。