JavaScript(jQuery)による継承とプロトタイプチェーンの基本と利点
- 公開日
- カテゴリ:jQuery
- タグ:JavaScript,jQuery,Prototype

Javascript は ES6 で class キーワードが導入されましたが、糖衣構文(シンタックスシュガー)であり現状ではプロトタイプベースのオブジェクト指向言語であると分類されています。
そんな JavaScript のオブジェクトは、プロトタイプオブジェクトのプロパティを遡るようにして保持しています。
今回は、JavaScript& jQuery を使って Prototype チェーンを実装していきます。
Contents
開発環境
今回の開発環境は以下の通りです。
- HTML 5
- jQuery v3.3.1
尚、今回はコンパイルが不要(ES6 不使用)の範囲での Prototype チェーンを実装していきます。
ベースコード
今回はプロトタイプチェーンのベースを示す事がメインなのでとりとめて何かの機能を作るものではありませんが、動作確認の為にボタンやリストの追加などを行います。
まずは基本となる HTML コードを記します。
- index.html
-
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>jquery prototypeチェーン</title> </head> <body> <div class="container"> <div class="main"> <ul class="list"></ul> </div> </div> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E=" crossorigin="anonymous"></script> <script src="js/prototype_init.js"></script> <script> </script> </body> </html>
読み込んでいるのは jQuery のコアソース(CDN)と、今回のスクリプトを記述する JS ファイルです。
また、HTML は container クラス内にある main クラスのを主軸として JS を展開していきます。続いて、JS ファイルへベースの JS を記述していきます。
- js/prototype_init.js
-
/** * コンストラクタ * @param name string 機能の識別名 * @param target_el jQueryElement 操作対象要素 * @param data array データ配列 * @constructor */ Main = function(name, target_el, data) { this.name = name; this.target_el = target_el; this.data = data; }; /** * インスタンス生成 * @param name string 識別名 * @param class_name string 対象クラス名 * @param data array データ配列 * @return {Main} */ function getInstance(name, class_name, data) { var class_name = "." + class_name; // インスタンス生成 var obj = new Main(name, $(class_name), data); return obj; }
変数 Main にコンストラクタとしての役割を持つ関数を代入しています。今回ここでは、内包するプロパティを設定しています。
そして、getInstance関数で new 演算子を用いて、Main のコンストラクタを動作させます。ここで生成されるオブジェクトが機能のメインオブジェクトとなります。
ではここで HTML に戻って、これらを使って Main オブジェクトを生成します。
- index.html
-
<script> // サンプル用に渡すデータ配列 var data = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ]; $(function() { // Mainオブジェクトを生成 var main = getInstance('サンプル機能', 'main', data); }); </script>
getInstance メソッドを使って Main オブジェクトを生成します。その際に、コンストラクタへ「機能名」「対象クラス名」「サンプルデータ配列」を渡しています。あとは JS で 定義した通り、これらを初期値として保持する Main オブジェクトが生成されます。
オブジェクトに機能と処理を持たせる
ベースとなるオブジェクトが作成できたら、このオブジェクトに機能を付与していきます。
今回は、ボタンをクリックしたらリストを表示する。という機能を付けるので、それぞれの処理メソッドや、イベントの登録を定義します。
Main オブジェクトにメソッドを追加するには以下のように記述します。
Main.prototype.methodName = function() {
// 処理
};
プロトタイプチェーンの定義の仕方は何通りかありますが、今回は外部から定義していく形にしています。 この方式で機能の追加、そしてイベントの登録を行ったもの(完成形)が以下になります。
- js/prototype_init.js
-
/** * コンストラクタ * @param name string 機能の識別名 * @param target_el jQueryElement 操作対象要素 * @param data array データ配列 * @constructor */ Main = function(name, target_el, data) { this.name = name; this.target_el = target_el; this.data = data; this.browser = this.findBrowser(); }; /** * 自身の識別名を返却する * @return string */ Main.prototype.getName = function() { return this.name; }; /** * 自身の操作対象要素を返却する * @return jQueryElement */ Main.prototype.getElementTarget = function() { return this.target_el; }; /** * データ配列を返却する * @return array */ Main.prototype.getListData = function() { return this.data; }; /** * ブラウザ名を返却する * @return string */ Main.prototype.getBrowser = function() { return this.browser; }; /** * ブラウザを判定する * @return string */ Main.prototype.findBrowser = function() { var userAgent = window.navigator.userAgent.toLowerCase(); var ret; switch(true) { case (userAgent.indexOf('msie') !== -1): case (userAgent.indexOf('trident') !== -1): ret = 'ie'; break; case (userAgent.indexOf('edge') !== -1): ret = 'edg'; break; case (userAgent.indexOf('chrome') !== -1): ret = 'chrome'; break; case (userAgent.indexOf('safari') !== -1): ret = 'safari'; break; case (userAgent.indexOf('firefox') !== -1): ret = 'firefox'; break; case (userAgent.indexOf('opera') !== -1): ret = 'opera'; break; default: ret = false; break; } return ret; }; /** * データからリストを生成する * @return void */ Main.prototype.addList = function() { // コールバック function callback(self) { return function(i, param) { self.getElementTarget().find('ul.list').append('<li>' + param + '</li>'); } } // ループ $.each(this.getListData(), callback(this)); // ついでに使用ブラウザもリストに加える this.getElementTarget().find('ul.list').append('<li>' + this.getBrowser() + '</li>'); }; /** * 初期化処理 * @return void */ Main.prototype.init = function() { // ボタンを追加する this.getElementTarget().append('<button class="btn">ボタン</button>'); }; /** * インスタンス生成 * @param name string 識別名 * @param class_name string 対象クラス名 * @param data array データ配列 * @return {Main} */ function getInstance(name, class_name, data) { var class_name = "." + class_name; // インスタンス生成 var obj = new Main(name, $(class_name), data); // 初期化処理 obj.init(); // イベント登録 $(document).on('click', class_name + ' button', function(e) { obj.addList(); }); return obj; }
イベントの登録は、getInstance関数の中で生成したオブジェクトを用いて行います。
このようにして、定義された機能は、Main オブジェクトを生成した後に、内部でも外部でも参照でき利用する事が可能です。つまりは、関数の中で別の関数を呼んだり、Main オブジェクトを取得した変数(外部)からもコールできます。
$(function() {
// Mainオブジェクトを生成
var main = getInstance('サンプル機能', 'main', data);
// 外部から利用
alert(main.getBrowser()); // ←Mainオブジェクトで定義したファンクションを外部からでも使用できる
});
プロトタイプチェーンを用いる事の利点
JS を書く上でプロトタイプチェーンを使う事の利点の1つは、ソースコードを1つの機能として収束できる点だと思います。
フレームワークを用いない Javascript や、jQuery でもそうですが、WEB アプリケーションの規模が大きくなればなるほどソースコードも増え、気が付けば JS だけで何千・何万という行数になる事も少なくありません。
最近では React や AngularJS も出てきて体系だったソース管理(MVW ・ MVVM ・ MVC という意味で)が行えるようになっていますが、jQuery などは工夫しないとアプリケーションの規模拡大と共にソースコードがただただ肥大化していくので、そこを嫌うエンジニアも少なくありません。(チーム開発をガンガン行うところではやはり管理が難しいのもわかる)
ただし、まだまだレガシーとして活用され、現在も開発が進められている jQuery は無下にできない優秀な JS フレームワークの1つだと私は考えています。
プロトタイプチェーン自体は別に jQuery のものではありませんが、Javascript を効率よく書く為に有効な手段の1つがこのプロトタイプチェーン(継承)になります。
また、プログラマ的な利点の1つとして、this スコープの明確化も上げられます。
Javascript を定義していくと、this のスコープを見失う場合(この this が何を指しているかわからなくなる・この this が意図しているスコープじゃなかった問題)がありますが、Prototype チェーンで定義すると this のスコープが同一オブジェクト内で自身(のオブジェクト)に向き、スコープが揃うので、1つの機能として迷子にならずにプログラミングを進められるという利点もあります。
そして、jQuery に至ってはやはり直接 DOM 操作を行う上でその対象が決まっていたりする事が多いので、対象の jQueryElement を自身に内包しておけば、それを呼び出すだけで良くなり、実装効率も向上します。
今回のデモでは何かの機能を作ったわけではないのであまりソースコード自体の解説を行いませんでしたが、対象となる要素の取り回しやデータの方向などはある程度記述しておいたので、是非読み解いて自分でも書いてみてください。