- ■ はじめに
- ■ オブジェクトを生成する方法
- ■ コンストラクタ関数って何?
- ■ プロトタイプの特徴
- ■ クラスでもprototypeという仕組みが裏側で動いている
- ■ MDNでStringコンストラクタ関数のメソッドを確認してみる
- ■まとめ
■ はじめに
はじめまして!
株式会社iimonでフロントエンドエンジニアをしている白水です。
この記事では、JavaScriptの「プロトタイプ」と「クラス」の関係性について書いていきます。
■ オブジェクトを生成する方法
# Pythonでオブジェクトを作る class Person: def __init__(self, name): self.name = name def greeting(self): return 'こんにちは、' + self.name taro = Person('太郎') print(taro.greeting()) # > こんにちは、太郎
オブジェクトを生成する方法には「クラスベース言語」と「プロトタイプベース言語」があります。
Python、Rubyなどが「クラスベース」に分類され「クラス」という設計図をもとにnew
演算子などを用いてオブジェクトが作られます。
一方で、JavaScriptは「プロトタイプベース」に分類されます。
プロトタイプベースでは「プロトタイプ」という仕組みがあり、その仕組みを使ってオブジェクトが作られています。
ES6(ES2015)からJSにもクラスが追加されましたが、その裏側では「プロトタイプ」という仕組みが動いています。
■ コンストラクタ関数って何?
// コンストラクタ関数 function Person(arg) { this.name = arg; } const taro = new Person("太郎"); taro.name; // > "太郎"
JavaScriptのES5バージョン(2009年に出たバージョン)までは、コンストラクタ関数とnew
演算子を用いて、オブジェクトを作っていました。
class Person { constructor(arg) { this.name = arg; } } const taro = new Person("太郎"); console.log(taro.name); // > "太郎"
コンストラクタ関数は、クラスの中で定義する「constoructor」と同じ動きをする関数のことを指しています。
そのため「コンストラクタ関数」でのthis
と、クラスのコンストラクタ内でのthis
は共にnew
したオブジェクト自身を指しています。
このようにコンストラクタ関数と、classの中の「constoructor」は同じようなものです。
ちなみに、constoructor
とはクラスをnew
演算子を使ってインスタンス化した際に、1番最初に自動的に呼び出される特別なメソッドのことです。
■ プロトタイプの特徴
◆ コンストラクタ関数を定義すると勝手にprototype
プロパティが定義される
// コンストラクタ関数 function Person() {} // 関数もオブジェクトの一種なので、プロパティに値を設定できる Person.userName = '太郎'; Person.userName; // > 太郎
JavaScriptでは「関数」もオブジェクトの一種です。(非プリミティブ型に分類されるため)
そのため、関数をオブジェクトのように扱うことができます。
「オブジェクトのように扱うことができる」というのは、関数にプロパティやメソッドを持たせることができるということです。
// コンストラクタ関数 function Person() { } // Personコンストラクタ関数にprototypeプロパティがあるか? console.log('prototype' in Person); // > true // prototypeプロパティがある Person.prototype; // > {constructor: ƒ} // prototypeプロパティに設定されている値はオブジェクト typeof Person.prototype; // > object
その中で、プロトタイプというのは、関数を定義すると勝手にprototype
プロパティが定義されます。
また、prototypeプロパティに設定されている値はobject
であることがわかります。
// コンストラクタ関数 function Person(arg) { this.name = arg; } Person.prototype.sayName = function() { console.log(`名前は、${this.name}です。`); } const jiro = new Person('次郎'); jiro.sayName(); // > 名前は、次郎です。
// クラス class Person { constructor(arg) { this.name = arg; } sayName() { console.log(`名前は、${this.name}です。`); } } const jiro = new Person('次郎'); jiro.sayName(); // > おはよう、次郎
prototypeに登録したsayNameという無名関数内で使われるthisは、呼び出し元のオブジェクトを参照しているため、prototypeに登録された関数はPersonクラスのsayNameメソッドと同じ役割を担っています。
String.prototype.at() - JavaScript | MDN
◆ インスタンス化するとprotptypeプロパティへの参照がprotoにコピーされる
function Person() { } Person.prototype.greet = function() { console.log('こんにちは'); } const human = new Person(); // 同じオブジェクトを参照している Person.prototype === human.__proto__; // > true // __proto__を通してメソッドを呼び出している human.greet(); // > こんにちは human.__proto__.greet(); // > こんにちは // 省略してもしなくても、同じメソッドを参照している human.greet === human.__proto__.greet; // > true
newでコンストラクタ関数からインスタンスを作ると、コンストラクタ関数のprototypeに設定されているオブジェクトへの参照が、インスタンスの__proto__
という
特別なプロパティにコピーされます。(参照のコピー)
そのため、Person.prototype
とhuman.__proto__
のオブジェクトは同じであり、メソッドを実行するときは、human.__proto__.greet();
のように__proto__
を通して実行できます。
■ クラスでもprototypeという仕組みが裏側で動いている
class Person { greet() { console.log('こんにちは'); } } // クラスにもprototypeが勝手にできる Person.prototype // > {constructor: ƒ, greet: ƒ} const instance = new Person(); // 同じオブジェクトを参照している Person.prototype === instance.__proto__; // > true // クラスでも__proto__を通してメソッドを実行している instance.greet(); // > こんにちは instance.__proto__.greet(); // > こんにちは
クラスでも裏側ではprototype
という仕組みが動作しています。
そのため、クラスを定義してもprototype
というプロパティが存在し、newすることで、__proto__
という特別なプロパティが定義されて、prototype
への参照が保持されます。
■ MDNでStringコンストラクタ関数のメソッドを確認してみる
String.prototype; // > String {'', constructor: ƒ, anchor: ƒ, at: ƒ, big: ƒ, …} const str1 = new String(' Hello '); str1.__proto__; // > String {'', constructor: ƒ, anchor: ƒ, at: ƒ, big: ƒ, …} str1.__proto__ === String.prototype; // > true str1.trim(); // > 'Hello' // 文字列リテラルの場合は、裏側でStringコンストラクタのオブジェクトに変換されている。 const str2 = ' Hello '; str2; // > ' Hello ' str2.trim();
MDNを見てみると、prototypeという文字をよく見ると思います。
trimというメソッドは、Stringコンストラクタのprototypeプロパティの値としてtrimというメソッドが設定されていることを表しています。
なので、Stringコンストラクタをnewすると、インスタンスの__proto__
というプロパティの値に、Stringコンストラクタのprototypeプロパティの値への参照がコピーされるので、文字列オブジェクトからtrimメソッドが呼び出せます。
String.prototype.trim() - JavaScript | MDN