JavaScriptの学習において、最も理解が難しい概念の一つとして知られるのが this
キーワードです。
this
は関数がどのように呼び出されたかに依存して、その参照先が変わる特性を持っています。
この柔軟性がある意味で混乱を招き、多くの初心者にとって大きな壁となります。
しかし、this
の動作を正しく理解することは、JavaScriptを深く理解し、効果的にコーディングするために不可欠です。
本章では、まず this
キーワードの基本概念と定義を明らかにし、その後、さまざまなシチュエーションにおける this
の挙動を詳細に解説します。
実践的なコードサンプルを通じて、this
の理解を深め、複雑な問題にも対処できるスキルを養っていきましょう。
this とは何か
this
キーワードはJavaScriptにおける特殊な識別子であり、関数やメソッドが呼び出された際に、その関数やメソッドが属するオブジェクトを参照します。
簡単に言えば、this
は関数やメソッドが実行されている「文脈」を指し示すものです。
しかし、その値がどのオブジェクトを指すかは、関数やメソッドがどのように呼び出されたかによって変わります。
基本例 1: シンプルなオブジェクトの例
まず、シンプルなオブジェクトを使って this
の動作を見てみましょう。
const person = {
name: 'Alice',
greet: function() {
console.log(this.name);
}
};
person.greet(); // 'Alice' が出力される
person
オブジェクトにはname
プロパティとgreet
メソッドがあります。greet
メソッド内のthis
はperson
オブジェクトを指します。person.greet()
が呼び出されると、this.name
はperson.name
を参照するため、'Alice'
が出力されます。
基本例 2: 車オブジェクトの例
次に、car
オブジェクトを使った例を見てみましょう。
この例では、複数のメソッドを持つオブジェクト内で this
をどのように使うかを示します。
const car = {
brand: 'Toyota',
model: 'Corolla',
start: function() {
console.log(`${this.brand} ${this.model} is starting...`);
},
drive: function() {
console.log(`${this.brand} ${this.model} is driving...`);
}
};
car.start(); // 'Toyota Corolla is starting...' が出力される
car.drive(); // 'Toyota Corolla is driving...' が出力される
- オブジェクトの定義:
car
オブジェクトにはbrand
プロパティとmodel
プロパティ、そしてstart
とdrive
というメソッドがあります。
- メソッド内の
this
:start
メソッド内のthis
はcar
オブジェクトを指します。drive
メソッド内のthis
も同様にcar
オブジェクトを指します。
- メソッドの呼び出し:
car.start()
を呼び出すと、this.brand
はcar.brand
を参照し、this.model
はcar.model
を参照するため、'Toyota Corolla is starting...'
が出力されます。car.drive()
を呼び出すと、同様にthis.brand
とthis.model
はcar
オブジェクト内のプロパティを参照し、'Toyota Corolla is driving...'
が出力されます。
この例では、car
オブジェクトのメソッドが this
を使ってオブジェクト自身のプロパティにアクセスすることで、動的にメッセージを生成しています。
このように、this
はメソッド内でオブジェクトのプロパティや他のメソッドにアクセスするための強力な手段です。
this
を使用しない場合のコード例
同じ car
オブジェクトの例を、this
を使用せずに書いてみます。
this
を使用しない場合、各メソッドはオブジェクトのプロパティに直接アクセスする必要があります。
const car = {
brand: 'Toyota',
model: 'Corolla',
start: function(carObject) {
console.log(`${carObject.brand} ${carObject.model} is starting...`);
},
drive: function(carObject) {
console.log(`${carObject.brand} ${carObject.model} is driving...`);
}
};
car.start(car); // 'Toyota Corolla is starting...' が出力される
car.drive(car); // 'Toyota Corolla is driving...' が出力される
- オブジェクトの定義:
car
オブジェクトにはbrand
プロパティとmodel
プロパティ、そしてstart
とdrive
というメソッドがあります。
- メソッド内の引数:
this
を使用しない場合、各メソッドはオブジェクト自身を引数として受け取る必要があります。start
メソッドとdrive
メソッドは、引数carObject
を使ってbrand
とmodel
にアクセスします。
- メソッドの呼び出し:
car.start(car)
を呼び出すと、引数として渡されたcar
オブジェクトのプロパティを使ってメッセージを生成します。- 同様に、
car.drive(car)
も引数として渡されたcar
オブジェクトのプロパティを使ってメッセージを生成します。
this
を使用しない場合、各メソッドでオブジェクト自身を引数として受け取る必要があります。
これにより、コードが冗長になり、メソッドの再利用性が低下します。
this
を使用することで、オブジェクト内のメソッドは自動的にそのオブジェクトのプロパティにアクセスできるため、コードが簡潔で可読性が高くなります。
基本例 3: コンストラクタ内の例
コンストラクタ関数は、新しいオブジェクトを作成し、そのオブジェクトを初期化するために使用されます。
コンストラクタ関数内では、this
キーワードは新しく作成されたオブジェクトを参照します。
これにより、コンストラクタ内で新しいオブジェクトのプロパティを設定することができます。
function Car(brand, model) {
this.brand = brand;
this.model = model;
this.start = function() {
console.log(`${this.brand} ${this.model} is starting...`);
};
this.drive = function() {
console.log(`${this.brand} ${this.model} is driving...`);
};
}
const myCar = new Car('Toyota', 'Corolla');
console.log(myCar.brand); // 'Toyota'
console.log(myCar.model); // 'Corolla'
myCar.start(); // 'Toyota Corolla is starting...' が出力される
myCar.drive(); // 'Toyota Corolla is driving...' が出力される
- コンストラクタ関数の定義:
Car
という名前のコンストラクタ関数を定義しています。この関数はbrand
とmodel
の2つの引数を取ります。- コンストラクタ関数内で、
this.brand
とthis.model
が新しく作成されるオブジェクトのプロパティとして設定されます。
- メソッドの定義:
start
メソッドとdrive
メソッドも同様にthis
を使って定義されます。これにより、これらのメソッドは新しく作成されたオブジェクトのプロパティとして使用できます。
- 新しいオブジェクトの作成:
new
キーワードを使って、Car
コンストラクタ関数を呼び出すことで、新しいCar
オブジェクトを作成します。const myCar = new Car('Toyota', 'Corolla');
のように呼び出すと、新しいCar
オブジェクトが生成され、そのオブジェクトのbrand
とmodel
プロパティが設定されます。
詳細な動作
- コンストラクタ呼び出し:
new Car('Toyota', 'Corolla')
が呼び出されると、Car
関数内のthis
は新しく作成された空のオブジェクトを指します。this.brand
とthis.model
が設定され、新しいオブジェクトにプロパティが追加されます。
- メソッドの設定:
this.start
とthis.drive
にメソッドが追加され、これらのメソッドも新しく作成されたオブジェクトのプロパティになります。
- オブジェクトの使用:
myCar
オブジェクトのbrand
とmodel
をコンソールに出力すると、それぞれ ‘Toyota’ と ‘Corolla’ が表示されます。myCar.start()
とmyCar.drive()
を呼び出すと、それぞれ'Toyota Corolla is starting...'
と'Toyota Corolla is driving...'
が表示されます。
コンストラクタ内での this
は、新しく作成されたオブジェクトを参照します。
これにより、コンストラクタ関数内で新しいオブジェクトのプロパティやメソッドを設定することができます。
this
を使用することで、同じコンストラクタ関数を使って複数のオブジェクトを作成し、それぞれに独自のプロパティやメソッドを持たせることができます。
基本例 4: アロー関数の例
Arrow関数(アロー関数)は、ES6(ECMAScript 2015)で導入された新しい関数の書き方です。
通常の関数と異なり、Arrow関数は自分自身の this
コンテキストを持ちません。
代わりに、定義されたスコープ(親スコープ)の this
を継承します。
これにより、特定の状況での this
の挙動を理解しやすくし、バインディングの問題を解決することができます。
まずは通常の関数について見てみましょう。
通常の関数(関数宣言や関数式)は、その関数がどのように呼び出されたかに応じて this
が変わります。
関数がメソッドとして呼び出されると、そのメソッドを所有するオブジェクトが this
になります。
const person = {
name: 'Alice',
greet: function() {
console.log(this.name); // `this` は `person` オブジェクトを指す
}
};
person.greet(); // 'Alice' が出力される
この例では、greet
メソッド内の this
は person
オブジェクトを指しています。
次にアロー関数を見てみましょう。
Arrow関数は通常の関数とは異なり、自分自身の this
を持たず、定義されたスコープの this
を継承します。
そもそもアロー関数では引数を持たないため、自分自身のthis
を持つことはありません。
そして、この特性がクロージャとしての役割を果たしており、アロー関数はクロージャの一部として動作します。
const person = {
name: 'Alice',
greet: function() {
const innerFunction = () => {
console.log(this.name); // `this` は外側のスコープ(`greet` メソッド)の `this` を継承
};
innerFunction(); // 'Alice' が出力される
}
};
person.greet();
この例では、innerFunction
がArrow関数として定義されており、外側のスコープである greet
メソッドの this
を継承しています。
以下のように書いても同じことが言えます。
const person = {
name: 'Alice',
greet() {
const innerFunction = () => {
console.log(this.name);
};
innerFunction(); // 'Alice' が出力される
}
};
person.greet();
この例ではgreet
メソッドはオブジェクトのプロパティとして定義されているため、変数宣言 (const
や let
など) は不要です。
また、今回の例ではいずれもgreet
メソッドに const
を指定する必要はありません。
例として説明するために const
を使用していますが、実際には greet
メソッドはオブジェクトのプロパティとして定義されるので、const
などの変数宣言は不要です。
まとめ
thisについて理解することができたでしょうか?
一度読んだだけで理解することはなかなか難しいと思いますが、使っているうちになんとなくわかるようになってくることもあります。
特にオブジェクト化をしっかりと理解することができれば、コンストラクタの例がはっきりとわかるようになると思います。(私はオブジェクト化を理解してからthisを理解できました。)
JavaScriptを学ぶ上で避けて通ることができないthisですが、覚えると非常に便利ですので、頑張って使い方を覚えていきましょう。