JavaScriptでは、オブジェクトにメンバを追加する際に、prototypeというプロパティを用意しています。
prototypeプロパティの特徴
- prototypeプロパティに格納されたメンバはインスタンス化された先のオブジェクトに引き継がる。
- あくまで、プロトタイプオブジェクトが利用されるのは、値の参照時のみで、設定時は使用されない。
prototypeプロパティのメリット
無駄なメモリ使用を避けることができる点
なので、あるオブジェクトをインスタンス化したとしても、prototypeプロパティに追加したメソッドであれば、インスタンス化しても無駄にメソッドを生成する必要がないので、無駄なメモリ消費から解放されます。
プロトタイプオブジェクトは、あくまで「インスタンスから暗黙的に参照されるだけ」で、インスタンスにオブジェクトがコピーされるわけではありません。
メンバの追加、変更をインスタンスがリアルタイムに認識できる点
インスタンスにメンバをコピーしないので、プロトタイプオブジェクトへの変更をインスタンス側でリアルタイムに確認ができます。
例えば、newでインスタンスを生成した後に、元のオブジェクトへのメンバの変更があった場合も、生成されたインスタンスからもその変更をキャッチアップすることができます。
デメリット
しかし、下記のようなデメリットもあります。
コードが冗長になる。
いちいち下記のようにメンバを追加するのは面倒ですし、メンバの数が多くなってくると冗長になってきてしまいます。
1 |
オブジェクト名.prototype.メンバ名 |
オブジェクト名が変更になってしまった場合は、全ての定義のオブジェクト名を変えなければなりません。
コードの可読性に問題が出てくる。
どこからどこまでが、同じオブジェクトのメンバであるのか見えにくくになります。
そこで出てくるのが、「オブジェクトのリテラル表現」という記述方法になります。
仕組み
JavaScriptのオブジェクトは、全てのオブジェクトが「プロトタイプ」をベースにして作られます。全てのオブジェクトはPrototypeというプロパティを保持しています。
1 2 3 4 5 |
const obj = { a: "aaa", b: 12, [[Prototype]]: オブジェクト or null } |
それ(プロトタイプ)をコピーして新しいオブジェクトを作ります。
プロトタイプで記述すると、インスタンス化のメソッドは「コピー」が作られるのではなく、「参照」して利用されます。なぜなら、基本的にメソッドはどのインスタンス先でも内容は同じなので複製して作るのはメモリの無駄になるためです。
定義方法
1 |
オブジェクト名.prototype.メソッド名 = function() {} |
プロトタイプを設定
Object.setPrototypeOfを使う
2015年に実装された。
1 2 |
const obj = {key:"あああ"} Object.setPrototypeOf(obj,{key2:"いいい"}); |
セッター「__proto__」を使う
「__proto__」とは以下どちらかの構文でオブジェクトを生成した際に裏側で作られるプロパティです。全てのオブジェクトはこの「__proto__」を保持しています。
仕組み
あくまでPrototypeを返してくれるゲッターに過ぎません。直接プロトタイプを表示しているわけではないです。ゲッターなので以下のように指定すればプロトタイプを取得できます。
1 2 |
const obj = {a:1}; obj.__proto__; // プロトタイプが返る。 |
また、以下のように使えばセッターになります。
1 2 |
const obj = {key:"あああ"} obj.__proto__ = {key2:"いいい"}; |
基本は使わないこと
今は非推奨のプロパティになっています。
代わりに2012年に登場したObject.createという文法を使いましょう。
また、2015年以降に登場した以下の文法もあります。(ただ、基本的に一度生成したプロトタイプをいじるのはしないのでObject.createだけ実務では使うことになります。)
- Object.setPrototypeOf
- Object.getPrototypeOf
なぜ__proto__は非推奨になったのか?
オブジェクトにプロパティを追加する際は予約語とかどんなプロパティ名でもよかった。しかし、__proto__を指定した時だけは例えばstringを入れようとした際は上書きすることはできないです。この動きがややこしいためあくまでプロトタイプをいじるときはメソッド経由にしましょうとなったわけです。
Object.createを使う
2012年に実装された。
引数で指定したオブジェクトがプロトタイプになります。それと同時に空のオブジェクトを返してくれます。
1 2 |
const obj = Object.create({key3:"ううう"}); // プロトタイプ内容 obj.name = "太郎"; //これがオブジェクト内容 |
基本的に一度生成したプロトタイプをいじるのはしないのでsetPrototypeOfは使わず、Object.createだけ実務では使うことになります。
プロトタイプの確認方法
Object.getPrototypeOfを使う
2015年に実装された。
1 2 |
const obj = {key:"あああ"} Object.getPrototypeOf(obj); |
ゲッター「__proto__」を使う
古くからある。非推奨になった。
1 2 |
const obj = {key:"あああ"} obj__proto__; |
プロトタイプ前提となっている構文
for inループ
オブジェクトのキーをループさせることができるES6からの構文ですが、プロトタイプに記載されているプロパティも含めてループをしてくれます。
ただ、実際はプロトタイプまでループして欲しくないケースの方が多いのでもしループさせる場合はfor of とObject.keysを組み合わせてループさせることが多いでしょう。
その他構文
基本的に、for in以外はプロトタイプまで遡って参照してくれません。
- Object.entries
- Object.getOwnPropertyNames
- for of Object.keys
ちなみに「Own」という単語はプロトタイプまで見ないという意味になります。
オブジェクトのリテラル表現
構文
1 2 3 4 5 6 7 8 |
オブジェクト名.prototype ={ メソッド名1: function(){ return 戻り値; }, メソッド名2: function(){ return 戻り値; } } |
メリット
- 「オブジェクト名.prototype.メソッド名」という記述方法を抑えることができる。
- オブジェクト名に変更があった場合でも、変更箇所を減らすことができる。
- 同一オブジェクトのメンバが1ブロックになっているので、コードの可読性が上がる。
プロトタイプチェーンとは?
JavaScriptの世界における継承の仕組みのことです。
JavaScriptでは、プロトタイプにインスタンスを設定することで、インスタンス同士を「暗黙の参照」で連結して、互いに継承関係を持たせることができるようになるのです。
仕組み
オブジェクトで指定されたキーはPrototypeプロパティで指定された先のオブジェクトまでキーがないかを調べます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const obj = { a: "aaa", b: 12, [[Prototype]]: obj2 } const obj2 = { c: "aaa", [[Prototype]]: obj3 } const obj3 = { d: "aaa", [[Prototype]]: null } obj.d // aaaが返る。 obj.c = 5; // obj2の値が書き変わるのではなく、objに新しくcというプロパティが追加される。 |
新しくキーを追加するときは一番参照先のオブジェクトの値が書き変わるのではなく元のオブジェクトにキーが追加されるので注意です。
thisの扱い
obj3の関数で仮にthisを使った場合は先頭のオブジェクトである「obj」が返ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const obj = { a: "aaa", b: 12, [[Prototype]]: obj2 } const obj2 = { c: "aaa", [[Prototype]]: obj3 } const obj3 = { d: "aaa", getThis() { return this } // この中にはobjが入る。 [[Prototype]]: null } obj.d // aaaが返る。 |
構文
1 |
子オブジェクト.prototype = new 親オブジェクト(); |
上記の構文を使うことで、子オブジェクトをnewした際に、親オブジェクトのメソッドも使うことができるようになります。
JavaScriptの継承は動的なもの
Java等のオブジェクト指向の継承であれば、静的なもので、一度継承関係を築いた場合は、プログラムの中でその関係を変更することはできませんでした。
しかし、JavaScriptでは、動的に継承関係すらも変更することができます。その辺が要注意です。
この記事へのコメントはありません。