JavaScript のプロトタイプと this の違い
JavaScript の prototype
と this
は、オブジェクト指向プログラミングにおいて重要な概念ですが、しばしば混同されます。以下に、両者の違いを詳しく説明します。
prototype
- prototype プロパティ
関数にはprototype
プロパティがあり、このプロパティにオブジェクトを割り当てることで、その関数のインスタンスが共有するプロパティやメソッドを定義できます。 - プロトタイプチェーン
プロトタイプは、他のプロトタイプを継承することができ、プロトタイプチェーンを形成します。JavaScript エンジンは、オブジェクトのプロパティやメソッドにアクセスするとき、まずそのオブジェクト自身をチェックし、見つからない場合はプロトタイプチェーンをたどって検索します。 - オブジェクトのプロトタイプ
すべてのオブジェクトは、プロトタイプと呼ばれる別のオブジェクトを継承します。このプロトタイプは、オブジェクトが持つことができるメソッドやプロパティを定義します。
this
- 関数呼び出しのモード
- グローバルモード
グローバルスコープで呼び出された場合、this
はグローバルオブジェクト (ブラウザではwindow
オブジェクト) を指します。 - メソッド呼び出しモード
オブジェクトのメソッドとして呼び出された場合、this
はそのオブジェクト自身を指します。 - コンストラクター呼び出しモード
new
演算子を使って呼び出された場合、this
は新しく作成されたオブジェクトを指します。 - 明示的バインドモード
call()
、apply()
、またはbind()
メソッドを使って呼び出された場合、this
は指定されたオブジェクトにバインドされます。
- グローバルモード
- 実行コンテキスト
this
キーワードは、関数が実行されるコンテキスト内のオブジェクトを参照します。このコンテキストは、関数がどのように呼び出されたかに依存します。
例
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log("Hello, my name is " + this.name);
};
const person1 = new Person("Alice", 30);
const person2 = new Person("Bob", 25);
person1.greet(); // Output: Hello, my name is Alice
person2.greet(); // Output: Hello, my name is Bob
この例では、Person
関数のプロトタイプに greet
メソッドを定義しています。person1
と person2
の両方のオブジェクトは、このプロトタイプを継承するため、greet
メソッドを使用できます。this
キーワードは、各オブジェクトの name
プロパティにアクセスして、適切な名前を表示します。
プロトタイプを使った継承の例
// Person コンストラクター関数
function Person(name, age) {
this.name = name;
this.age = age;
}
// Person.prototype に greet メソッドを追加
Person.prototype.greet = function() {
console.log("こんにちは、私は" + this.name + "です。");
};
// Student コンストラクター関数 (Person を継承)
function Student(name, age, grade) {
Person.call(this, name, age); // Person のコンストラクターを呼び出す
this.grade = grade;
}
// Student.prototype に study メソッドを追加
Student.prototype.study = function() {
console.log("私は" + this.grade + "年生です。勉強中です。");
};
// Person と Student のインスタンスを作成
const person1 = new Person("太郎", 30);
const student1 = new Student("次郎", 20, 2);
person1.greet(); // こんにちは、私は太郎です。
student1.greet(); // こんにちは、私は次郎です。
student1.study(); // 私は2年生です。勉強中です。
解説
call()
メソッドは、別の関数のコンテキストで関数を呼び出す際に使用されます。this
は、それぞれのコンストラクターやメソッド内で、そのコンテキストでのオブジェクト自身を指します。Student
はPerson
のプロパティとメソッドを継承し、独自のstudy
メソッドを持っています。Person
は親クラス、Student
は子クラスのような役割を果たします。
this
のバインディングの例
const person = {
name: "太郎",
greet: function() {
console.log("こんにちは、私は" + this.name + "です。");
}
};
person.greet(); // こんにちは、私は太郎です。
// `this` を別のオブジェクトにバインド
const anotherPerson = { name: "次郎" };
person.greet.call(anotherPerson); // こんにちは、私は次郎です。
- これにより、
greet
メソッドの中でthis.name
にアクセスすると、anotherPerson
のname
プロパティにアクセスすることになります。 call()
メソッドを使って、greet
メソッドのthis
をanotherPerson
にバインドしています。
- this
関数の実行コンテキストにおけるオブジェクト自身を指すキーワードです。call()
やapply()
などのメソッドを使って、this
のバインディングを動的に変更できます。 - prototype
オブジェクト間の継承を実現し、プロパティやメソッドを共有する仕組みです。
ポイント
this
の挙動は、関数の呼び出し方によって異なります。特に、アロー関数 (=>
) のthis
の挙動は通常の関数とは異なるため、注意が必要です。prototype
は、JavaScript のオブジェクト指向プログラミングにおいて、クラスベースの言語におけるクラスに相当する概念です。
より深く理解するために
- アロー関数
new
演算子bind()
メソッド- プロトタイプチェーン
これらの概念を学ぶことで、JavaScript のオブジェクト指向プログラミングのより深い理解が得られます。
クラス構文 (ES6以降)
- デメリット
- メリット
- 継承がより明確になる
prototype
を直接操作する必要がなくなる- より簡潔なコードが書ける
- 特徴
クラスベースのオブジェクト指向をより直感的に記述できます。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`こんにちは、私は${this.name}です。`);
}
}
class Student extends Person {
constructor(name, age, grade) {
super(name, age);
this.grade = grade;
}
study() {
console.log(`私は${this.grade}年生です。勉強中です。`);
}
}
オブジェクトリテラル
- デメリット
- メリット
- 特徴
シンプルなオブジェクトの作成に適しています。
const person = {
name: '太郎',
age: 30,
greet() {
console.log(`こんにちは、私は${this.name}です。`);
}
};
関数コンポジション
- デメリット
- 初心者には学習コストが高い
- メリット
- 再利用性の高いコードが書ける
- 状態の管理が容易になる
- 特徴
純粋関数と高階関数を利用して、より関数的なスタイルでプログラミングを行います。
const greet = person => () => {
console.log(`こんにちは、私は${person.name}です。`);
};
const taro = { name: '太郎' };
greet(taro)();
プロトタイプ継承の回避
- デメリット
- メリット
- 特徴
Object.create()
を使用して、プロトタイプチェーンを意識せずにオブジェクトを作成できます。
const personProto = {
greet() {
console.log(`こんにちは、私は${this.name}です。`);
}
};
const person = Object.create(personProto);
person.name = '太郎';
person.greet();
どの方法が最適かは、プロジェクトの規模、チームのスキル、コードの可読性など、様々な要因によって異なります。
- プロトタイプ継承の回避
プロトタイプチェーンを完全に制御したい場合や、実験的なプログラミングを行う場合に適しています。 - 関数コンポジション
純粋関数的なスタイルでプログラミングしたい場合や、状態の管理を重視する場合に適しています。 - オブジェクトリテラル
シンプルなオブジェクトの作成や、データの構造化に適しています。 - クラス構文
大規模なアプリケーションや、より厳密なオブジェクト指向プログラミングが必要な場合に適しています。
これらの方法を組み合わせることで、より柔軟かつ効率的な JavaScript プログラミングが可能になります。
- プロトタイプチェーン は、JavaScript のオブジェクトの継承を理解する上で非常に重要な概念です。
this
の挙動は、アロー関数、bind
メソッドなど、様々な要素によって影響を受けます。
javascript prototype this