概要
複雑なアニメーションや機能を実装する際、関数を単純に記述するだけではどうしても非効率なコードになりがちです。
JavaScript は「プロトタイプベース」のオブジェクト指向言語です。
JavaScriptでは、オブジェクト指向を実現するためにprototypeというプロパティを利用します。
ES6等では PHPなどと同じように class 構文が利用可能ですが、実際にはこの prototype を利用して処理されています。
JavaScriptの言語仕様等絡んでくる中級以上であれば必ず必要になる知識です。
ただ、概念をつかむのに非常に苦労します。焦らずゆっくり習得しましょう!
プロトタイプはどういう時に役に立つか?
プロトタイプはある程度まとまった1つの機能を実装する時に役に立ちます。
1. JavaScriptでは、(ほぼ)すべてオブジェクトである
オブジェクトとは、JavaScriptで取り扱うすべてのデータを指します。
【test】という文字列は、console.log(“test”)とすればそのままコンソールに test と表示されますね。
$("span").text("test") とすれば span タグ内のテキストが test に置き換わります。
しかし、【test】という文字列は、実際には以下のようなオブジェクトとしてJavaScriptでは取り扱われます。
これにより、例えば文字数を取得したい場合は、test.length と記述すれば 4 という値が取得できますし、test[0]で最初の1文字目であるtが取得できます。
実際、プログラミングする時は【test】という文字列にしか見えませんが、実体はオブジェクトとして取り扱えるようになっているのです。
配列の場合も、整数の場合も、関数も同様です。
上記の関数の場合、下のようなオブジェクトで形成されています。
testFunc.nameで関数名が取得でき、testFunc.lengthで引数の数が取得できます。
オブジェクト以外のデータ
見出しに【(ほぼ)すべてのデータはオブジェクトである】とつけましたが、undefined, nullは例外です。 |
2. JavaScriptでは、すべてのオブジェクトがprototypeベースで作られている
さて、本題です。先ほどすべてのデータがオブジェクトであるという話をしましたが、
そのオブジェクトには `__proto__` というプロパティが設定されています。
ずらーとプロパティが並んでいますが、どこかで見たような名称ですね。
“test”.__proto__ には、文字列型のデータに対して普段利用しているメソッドが定義されています。
"test".__proto__.toUpperCase()
"test".__proto__.trim()
....
標準ビルトインオブジェクトのprototypeプロパティ
さらに、文字列型を生成する時に使う、String という オブジェクトにも、String.prototype というプロパティが用意されています。
- var test = "test" と文字列を変数に格納する時に、
- 裏では var test = new String("test") というコードを実行しています。
- Stringは “” の糖衣構文であり、その他の型でも同様です。
- var arr = [] と var arr = new Array() は同義であり、
- var obj = {} と var obj = new Object() は同義です。
JavaScriptのリファレンスとして名高いMDNを見てみます。
String.prototype. のあとに文字列に対して利用できるメソッドが定義されていることがわかります。
オブジェクトは prototype を継承し生成される- プロトタイプチェーン -
先ほどの "test".__proto__ と同様のメソッドが格納されています。
試しに以下のコードを実行すると、true が返されます。
var test = new String("test"); console.log( test.__proto__ === String.prototype ) // tru
これがどういうことかというと、”test”という文字列は、暗黙の間に String.prototype というプロパティを継承し、生成されます。
これにより、文字列型ではすべての文字列で、”test”.trim() で空白を削除したり、”test”.toUpperCase()で大文字に変換することができるのです。
この、prototype というプロパティを継承することを プロトタイプチェーンと呼びます。
3. そして、オブジェクト指向へ…
JavaScriptでの class の実装は、このプロトタイプチェーンを利用して実装します。
次のドキュメントで実際の案件等を引き合いに出し解説します。
function A(a){ this.varA = a; } // 上の A 関数の定義で示すように、A.prototype.varA は常に、 // this.varA によって隠されるのに、 varA を prototype に含む目的は何か? A.prototype = { varA : null, // 我々は何もせず、プロトタイプから varA を叩き落とすべきではないのでしょうか? // 恐らく、最適化として隠れたクラスにスペースを割り当てることを意図したものです。 // https://developers.google.com/speed/articles/optimizing-javascript#Initializing // もし varA がどのインスタンスでも独自に初期化されなかったとしても、インスタンスの変数は妥当となるでしょう。 doSomething : function(){ // ... } }; function B(a, b){ A.call(this, a); this.varB = b; } B.prototype = Object.create(A.prototype, { varB : { value: null, enumerable: true, configurable: true, writable: true }, doSomething : { value: function(){ // オーバーライド A.prototype.doSomething.apply(this, arguments); // call super // ... }, enumerable: true, configurable: true, writable: true } }); B.prototype.constructor = B; var b = new B(); b.doSomething();
豆知識 : クラスベースとプロトタイプベース
PHPやその他の言語ではclass Person のような形式でクラスが定義できます。(これをクラスベースと呼びます)。
それとは違い、以前からプロトタイプベース(オブジェクトを継承する)のプログラミング言語が存在しており、JavaScriptはそちらの仕様を採用しているだけです。
オブジェクト志向の考え方やクラスとしての利用方法は他の言語と同じような感覚で実装します。
クラスベースでオブジェクト指向を実装するか、プロトタイプベースでオブジェクト指向を実現するかの差です。https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%83%88%E3%82%BF%E3%82%A4%E3%83%97%E3%83%99%E3%83%BC%E3%82%B9
4. まとめ
- JavaScriptではほぼすべてのデータがオブジェクトである
- 各オブジェクトの仕様では、デフォルトで.prototypeというプロパティがあり、その中でメソッドや.lengthなどのプロパティが定義されている
- データをオブジェクト化する時、.prototype プロパティが継承され、生成されたオブジェクトには__proto__というプロパティに引き継がれる
- 一連の流れをプロトタイプチェーンと呼ぶ
参考
Udemyを実際に体験した方の感想記事もぜひご覧ください♪