JavaScriptのthis
キーワードやpromise/async/await
、スコープ
、巻き上げ
などの概念を学び始めると、JavaScriptの中級者レベルに到達したと言えます。
ただし、この段階は概念を理解するための難易度であり、技術的な中級者になるための一歩となります。
この中でも、Promiseの概念の理解は、this
キーワードと同じぐらい難易度が高く、学習者によっては急に難しく感じるかもしれません。
thisとはなんですか?
JavaScriptにおける最も難しい概念の1つが、この「this」キーワードです。
しかし、この概念を理解することは、優れた開発者になるための鍵となることを覚えておいてください。
「this」とは、それが属するオブジェクトを指すキーワードです。
また、実行コンテキストのプロパティとしても定義されます。
実行コンテキストとは、コードを実行する際の環境のことであり、JavaScriptでは「this」は現在コードを実行しているオブジェクトを参照します。
グローバルコンテキストでは、グローバルスコープを参照します。
関数コンテキストでは、関数スコープを参照します。
「this」は、関数がどのように呼び出されるかによって異なるオブジェクトを参照するため、実行コンテキストが変更されるたびにその変更を参照します。
厳密モードでない場合、「this」は常にオブジェクトへの参照として扱われます。
「this」は主にオブジェクト指向プログラミングのコンテキストで使用されることに留意してください。
これは、オブジェクトのプロパティやメソッドを参照するために使用されます。
例えば、独自のオブジェクトを持っていて、あるメソッドの中でプロパティを使いたい場合、この「this」を使う必要があります。
一方、純粋な関数や変数を参照する場合は、この「this」は省略されます。
下記の例を見てみましょう。
function changeDrink(){ //ここでは「this」を参照する必要はありません。 this.drink = "coffee"; } const myDrink = { drink: "coke" }; myDrink.changeDrink = changeDrink; //ここで、この「this」は呼び出されたmyDrinkを参照しています。 console.log(this); console.log(myDrink);
正確に「this」が何を指しているのかを理解するには、関連する関数がどのように呼び出されるかを知る必要があります。
それらを学んでいきましょう。
グローバル実行環境
JavaScriptにおいて「this」変数を理解するためには、まず実行環境の参照が重要になります。
Webブラウザ上でJavaScriptを実行する場合、その環境は"Object Window"によって表されます。
そのため、デフォルトの「this」は"Object Window"となります。
console.log(this === window); // true
一方、Node.js環境では、"globalオブジェクト"が実行環境を表します。
そのため、下記のようになります。
console.log(this === global); // false
JavaScriptにおけるグローバルオブジェクトは、Webブラウザの場合は"Object Window"であり、Node.js環境の場合は"globalオブジェクト"です。
オブジェクト指向プログラミングにおいて、「this」はオブジェクト自身を表すという概念がありますが、JavaScriptにおいては、実行環境や実行コンテキストに関連付けられることが多いです。
実際に、DOMの要素やイベントにアクセスする場合や、クラスを構築する場合、通常のオブジェクトのプロパティとメソッドを使用する場合でも、「this」を使用することがあります。
このように、JavaScriptにおける「this」は使用方法に応じてさまざまなオブジェクトを参照することができます。
単独のグローバルスコープ
ここからは、厳格モードと非厳格モードで比較しながらJavaScriptの「this」について学習していきます。
JavaScriptで「this」を単独で使用する場合、そのコンテキストでのグローバルオブジェクトを参照します。
"use strict"; // 厳格モード let x = this; console.log(x); // [object Window] this.name = 'Taro'; console.log(window.name); // Taro
上記の例では、厳格モードであるため、「this」はグローバルオブジェクトを指します。
ブラウザ環境ではグローバルオブジェクトは[Object Window]です。
そして、this.name
とwindow.name
は同じものを参照します。
一方、非厳格モードでも同じように動作します。
let x = this; console.log(x); // [object Window] this.name = 'Taro'; console.log(window.name); // Taro
したがって、JavaScriptでは「this」を単独で使用する場合、厳格モードでも非厳格モードでも、常にグローバルオブジェクトを参照することになります。
ただし、関数の内部で「this」を使用する場合は、その関数がどのように呼び出されたかによって「this」の値が変化します。
また、strictモードとnon-strictモードでも挙動が異なる場合があります。
関数内部のthis
JavaScriptでは、関数内で「this」変数にアクセスできます。
デフォルトでは、関数を呼び出したオブジェクトに関連付けられます。
しかし、厳密なモードでは異なる動作があります。
例えば、次のコードを見てみましょう。
function sample() { console.log(this); } sample(); // output: [object Window]
この例では、関数がグローバルスコープで呼び出されているため、「this」はブラウザのグローバルオブジェクトであるWindowを指します。
厳密なモードでの関数内部で使用される「this」変数は、通常の非厳密なモードと異なります。
厳密なモードでは、「this」は関数が呼び出された場所によって異なります。
例えば、次のコードを見てみましょう。
function sample() { "use strict"; console.log(this); } sample(); // output: undefined
この例では、"use strict";を使用して関数内で厳密なモードを有効にしています。
そのため、「this」はグローバルオブジェクトではなく、undefined
を参照します。
厳密なモードが関数内で有効になっている場合、その関数内でネストされたすべての関数にも同じ厳密なモードが適用されます。
注意すべき点としては、厳密なモードが使用されている関数内で、「this」を使用する場合には、undefined
が返される可能性があることです。
この点に留意して、適切にコードを記述するようにしてください。
コンストラクターでの呼び出し
JavaScriptにおいて、「new」キーワードを使って関数オブジェクトのインスタンスを生成する際に、その関数はコンストラクターと呼ばれます。
コンストラクター関数は、新しいインスタンスを生成して返します。
コンストラクター関数内で、「this」キーワードは新しく作成されたインスタンスを参照します。
これにより、異なるインスタンスを生成して、それぞれのプロパティやメソッドを持つことができます。
下記は、Person
関数をコンストラクターとして使用して、2つのインスタンスを作成する例です。
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();
とperson2.userName();
はそれぞれ、異なるインスタンスのプロパティにアクセスし、それぞれの名前を表示します。
一方、「new」キーワードを省略した場合、コンストラクター関数がグローバルオブジェクトを参照するため、「this」の値はundefined
になります。
let person3 = Person("Yuta", "Sato"); console.log(person3); // undefined
したがって、「new」キーワードを使ってコンストラクター関数を呼び出すことを忘れないようにしましょう。
クラスでのthis
ES6のクラスでは、メソッド内で「this」を使用すると、現在のオブジェクトを参照することができます。
例えば、次のようなPerson
クラスを考えてみましょう。
class Person { constructor(name) { this.name = name; } greet() { console.log(`Hi! My name is ${this.name}`); } }
このクラスのgreet()
メソッドでは、this.name
という式が使われています。
ここでのthis
は、呼び出し元のオブジェクト、つまりPerson
クラスのインスタンスを指します。
例えば、次のようにインスタンスを生成して、greet()
メソッドを呼び出すと、正しく自己紹介ができます。
const person = new Person('Alice'); person.greet(); // "Hi! My name is Alice"
ただし、クラスのメソッドを別の変数に代入した場合には、注意が必要です。
例えば、次のようにgreet()
メソッドをfoo
という変数に代入してみましょう。
const person = new Person('Bob'); const foo = person.greet;
この場合、foo()
を呼び出すと、「this」がundefined
になってしまいます。
foo();
// "TypeError: Cannot read property 'name' of undefined"
これは、クラスのメソッドを変数に代入した場合に、「this」が失われてしまうためです。
この問題を回避するためには、foo()
を呼び出す際に、「this」を明示的に指定する必要があります。
foo.call(person);
// "Hi! My name is Bob"
Reactのクラスコンポーネントでも、同様に「this」を扱うことがあります。
例えば、コンストラクタ内でイベントに関連するコールバックをバインドする場合には、「this」を明示的に指定する必要があります。
ただし、厳密モードでは、「this」が未定義の場合にグローバルオブジェクトを指す代わりに、undefined
が指定されます。
このため、厳密モードでクラスを扱う場合には、「this」が未定義になってしまう可能性があることに注意してください。
オブジェクトメソッドでの呼び出し
メソッドとは、オブジェクトが持つ関数またはオブジェクトが実行できるタスクのことを指します。
JavaScriptにおいて、オブジェクトのメソッド内で使用される「this」は、そのメソッドが呼び出されたオブジェクト自体を参照します。
たとえば、下記のようなperson
オブジェクトがあった場合、sample
メソッド内で使用される「this」はperson
オブジェクトを参照します。
const person = { name: '太郎', age: 30, sample() { // `person`オブジェクトが出力される console.log(this); console.log(this.name); // `太郎`が出力される } }; person.sample(); // `sample`メソッドが`person`オブジェクトで呼び出される
このように、オブジェクトのメソッド内で「this」を使用することで、そのメソッドが呼び出されたオブジェクト自体を参照することができます。
See the Pen JavaScript This アロー関数 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
実際に試してみたい方は下記からどうぞ
https://codepen.io/enjinia_f/pen/BarNzrN/?editors=1011
他の例を見てみましょう。
JavaScriptにおいて、オブジェクトのメソッドを変数に格納してから呼び出す場合、メソッド内の「this」はグローバルオブジェクトを参照するため、意図しない結果が得られることがあります。
しかし、bind()
メソッドを使用することで、メソッドが所有するオブジェクトを明示的に指定でき、「this」が期待通りの値を持つ新しい関数を生成することができます。
例えば、下記のようなオブジェクトがあるとします。
let myObj = { greet: 'Hello!!', getGreet: function() { return this.greet; } };
このオブジェクトのgetGreet()
メソッドを変数に格納してから呼び出す場合、次のようになります。
let greet = myObj.getGreet; console.log(greet()); // undefined
この場合、greet()
はグローバルオブジェクトを参照するため、undefined
を返します。
しかし、bind()
メソッドを使用することで、「this」の値をmyObj
に明示的にバインドすることができます。
let greet = myObj.getGreet.bind(myObj); console.log(greet()); // Hello!!
このように、bind()
メソッドは、与えられたオブジェクトを「this」として持つ新しい関数を返します。
これにより、メソッドを変数に格納してから呼び出す場合でも、意図通りの結果を得ることができます。
thisのカスタム値
JavaScriptにおいて、すべての関数はcall()
およびapply()
またはbind()
メソッドを持っています。
これらのメソッドを使用することにより、関数の実行コンテキストで「this」をカスタム値に設定することができます。
例えば、下記のコードでは、getPerson()
関数を定義し、call()
メソッドを使用してuser1
およびuser2
のオブジェクトを「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
call()
メソッドの第1引数には、getPerson()
関数内で「this」として参照されるオブジェクトを渡します。
同様に、apply()
メソッドでも同じことができます。ただし、第2引数に配列を渡します。
getPerson.apply(user1, ["Name: "]); getPerson.apply(user2, ["Name: "]);
call()
メソッドは、引数を個別に渡すことができますが、apply()
メソッドは引数を配列で渡します。
重要な点として、厳密モード以外でこれらのメソッドを使用する場合、null
およびundefined
を渡すとJavaScriptエンジンがそれを無視するため、注意が必要です。
そのため、常に厳密モードでコードを書くことを推奨します。
アロー関数でのthis
ES6で導入されたアロー関数は、通常の関数と比べてコンテキストの点で異なる動作をします。
通常の関数は実行されるたびに実行コンテキストが作成されますが、アロー関数は独自の実行コンテキストを定義しないため、親スコープから実行コンテキストを取得します。
そのため、アロー関数内部で「this」を使用する場合は、親スコープオブジェクトを参照することになります。
例えば、下記のようにオブジェクトのメソッド内でアロー関数を使用すると、そのアロー関数内部での「this」は、そのメソッドが属するオブジェクトを指します。
const person = { name: 'Taro', getName() { let x = () => console.log(this.name); x(); } } person.getName(); // Taro
このように、アロー関数を使用することで、「this」を安全かつ簡潔に扱うことができます。
また、アロー関数を使用すると、通常の関数とは異なり、メソッド内部で関数を使用した場合に「this」がundefined
になる問題を解決することができます。
下記の例では、getPerson
メソッド内でアロー関数を使用することで、sampleFunc
内部でも「this」がgetPerson
メソッドが属するperson
オブジェクトを指すようになっています。
const person = { name : 'Taro', age: 30, city: 'Tokyo', getPerson() { console.log(this); // personオブジェクト console.log(this.city); // Tokyo let sampleFunc = () => { console.log(this); // personオブジェクト console.log(this.city) // Tokyo } sampleFunc(); } } person.getPerson();
アロー関数は、「this」をより安全かつ直感的に扱えるようにするための強力なツールです。
ただし、コンテキストの点で異なる動作をするため、注意深く使用する必要があります。
DOMイベントハンドラー
イベントリスナーのコールバック関数では、「this」を使用することがあります。
この方法は、この概念を理解するのが簡単な場合があります。
ブラウザでは、イベントハンドラのために特別な「this」コンテキストが用意されています。
addEventListenerによって呼び出されたイベントハンドラでは、「this」はevent.currentTarget
を参照します。
多くの開発者は、必要に応じてevent.target
またはevent.currentTarget
を使用して、DOM内の要素にアクセスします。
ただし、イベントリスナーのコールバック関数では、コンテキストで「this」参照が変化するため、注意が必要です。
例えば、下記のHTMLコードがあるとします。
<button id="hello">Click me</button>
次のようなJavaScriptコードを記述すると、ボタンをクリックしたときにコンソールに要素が表示されます。
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の優先順位
JavaScriptにおいて、「this」キーワードがどのオブジェクトを指しているかを判断するためには、以下の4つのルールが適用されます。
優先順位は以下の通りです。
bind()
メソッドによる明示的な「this」の設定call()
メソッドまたはapply()
メソッドによる暗黙的な「this」の設定オブジェクトのメソッドとして呼び出された場合の、そのオブジェクトが「this」となる
上記のいずれにも当てはまらない場合、グローバルスコープの「this」が適用される
まず、bind()
メソッドを使用して、関数がコールバックとして呼び出されている場合に明示的に「this」を設定しているかどうかをチェックします。
次に、call()
メソッドまたはapply()
メソッドを使用して、関数が引数付きで呼び出されている場合に暗黙的に「this」が設定されているかどうかをチェックします。
その後、関数がオブジェクトのメソッドとして呼び出されている場合には、そのオブジェクトが「this」として設定されることになります。
最後に、上記のいずれにも該当しない場合には、グローバルスコープが「this」となります。
以上のように、「this」の優先順位は、明示的に bind()
メソッドで設定されたものが最優先であり、次いでcall()
メソッドやapply()
メソッドによる暗黙的な設定が適用されます。
オブジェクトのメソッドとして呼び出された場合には、そのオブジェクトが「this」となります。
最後に、上記のいずれにも当てはまらない場合には、グローバルスコープが「this」となることになります。
最後に
JavaScriptにおける「this」キーワードは、厳密モードで実行すると挙動が変わることに注意が必要です。
通常、関数の呼び出し方によって「this」の値が決まります。
つまり、関数が呼び出されるたびに、「this」の値は異なる可能性があります。
ES2015以降、実行コンテキストの戦略は一部変更されましたが、大部分は同じままです。
コード内で明示的かつ明確に記述することで、「this」を変更して実行コンテキストを明確にすることができます。
初めは複雑に思えるかもしれませんが、どのような条件で「this」が参照されるかを知ることは重要です。
これにより、プログラム内で「this」の値を正確に決定することができます。
JavaScriptコードで「this」キーワードをシンプルに使用する方法を理解することは非常に重要です。
この記事が役立つことを願っています。
最後まで読んでいただきありがとうございました。
この記事が役に立った場合はブックマークして、他の方にも共有して頂けると幸いです。