【JavaScript】他言語からJavaScriptへ移行した時のオブジェクト指向での勘所 2回目
どうも、最近研究のソースコードが酷いスパゲッティになってしまい、
一から作り直すために紙上で設計をしている最中です。
特に研究の分野では上手くいくか分からない手法を短期間で実装してくることを要求されるので、
どんどんソースが汚くなってくる上に全体の構成を考えながらやる余裕もないことが多いです。
リファクタリングの知識はどの言語でも必要なものなので色々と深く学びたい。
と、いっても現在それ系で読んでいる本なんて「リーダブルコード」くらいなのですが。
それでは、前回からの続きを始めていきましょう!
前回 -> 【JavaScript】他言語からJavaScriptへ移行した時のオブジェクト指向での勘所 1回目 - datchの日記
前回までのおさらい
さてさて、前回までは以下の項目について解説を行っていきました。
- クラスの作成方法
- カプセル化
Javaのクラス記法からJSのオブジェクト指向(自称:JSOOP)でどのように書くべきなのか?
という点に焦点を当てて解説を行ってきました。
JSOOPでのカプセル化によるメモリ使用量の増大という問題点についても触れ、JSでのオブジェクト指向の記法・問題点について触れました(継承とかも触れたら良かったと今更ながら後悔。)
そしてJSOOPを実現しようとすると以下の仕組みを利用する必要があります。
- オブジェクトのプロトタイプ
- プロトタイプチェーン
- 即時関数
- クロージャ
今回はそれぞれの機能について解説して行きたいと思います。
機能紹介のその前に
この記事を書く前に以下のスライドを閲覧し、非常に勉強になりました。
C++系統のオブジェクト指向に慣れ親しんでいる人がJSの仕様で
引っ掛かりそうな箇所をしっかり紐解いて説明してくれています。
著者の方には感謝感謝です。
特に継承で利用するプロトタイプチェーンや、インスタンス変数にアクセスするための
this参照についての解説はありがたかったです。
正直、これを読んでしまうと自分の解説いらない気もしますが、
メモ用ということで自分の方でも解説していきたいと思います。
オブジェクトのプロトタイプ
プロトタイプオブジェクト
では、オブジェクトのプロトタイプについて説明します。
JSでは関数オブジェクトを作成する時に暗黙で"prototype"プロパティが作成される。
これをプロトタイプオブジェクトと呼ぶ。
では本当にプロトタイプオブジェクトが暗黙で作成されるかを見てみよう。
var TestClassPrototype = (function() { function TestClassPrototype() { console.log(this.prototype); // undefined // 関数オブジェクトの中からはprototypeを参照することは出来ない } console.log(TestClassPrototype.prototype); // TestClassPrototype {} // 空のprototypeオブジェクトが暗黙で作成されている return TestClassPrototype; })(); function init() { function testFunctionObjectPrototype(){} console.log(testFunctionObjectPrototype.prototype); // TestFunctionObjectPrototype {} // もちろん即時関数の中だけでなくても同様に関数を宣言した瞬間にオブジェクトが作成されていることが分かる testFunctionObjectPrototype.prototype.hoge = 3; console.log(testFunctionObjectPrototype.prototype); // testFunctionObjectPrototype {hoge: 3} // prototypeにプロパティを追加すればもちろん反映される var testObjectPrototype = {}; console.log(testObjectPrototype.prototype); // undefined // オブジェクトにはprototypeは作成されない }
結果から全ての関数オブジェクトにプロトタイプオブジェクトが作成されることがわかった。
ではこの関数オブジェクトだけに暗黙宣言されるプロトタイプオブジェクトはどのような役に役にたつのだろうか?
それはオブジェクト指向の継承に用いられる。
JSOOPでの継承のやり方
前回では触れられなかったがJSOOPではもちろん継承も行える。
では、早速いつもどおりコードで確認をしてみよう。
class Base { function Base(){]; return Base; }; class Derivation extends Base { Derivation(){super();}; // コンストラクタ };
var Derivation = (function(super) { // 継承部分 Derivation.prototype = Object.create(Base.prototype); // このsuperclassの書き方には少し問題があるらしいがここではその説明を省く Derivation.prototype.superclass = Base; // このままではBaseを指したままなので、Derivationになるように変更する Derivation.prototype.constructor = Derivation; function Derivation(){this.superclass();} // コンストラクタ reuturn Derivation; })();
こんな感じだ。
実は自分はこの書き方をしておらず以下のように継承を行っていた。
var Derivation = (function() { Derivation.prototype = new Base(); // ::::: })();
この書き方はウェブ検索でかなり引っかかるやり方なのだが実はあまりよろしくない。
と分かっているが、自分も現在の腹落ち度ではそこまで深く説明できないのでここらへんは勘弁していただきたい←
とりあえず、prototype = new Base()のような書き方をしてはいけない程度の認識でお願いしたい。
プロトタイプチェーン
紹介のスライドにわかりやすく書いてあるので、自分の解説がいらないような気もしますが、
メモということで独り言のように説明を行っていきたいと思います。
プロトタイプチェーンって何ですか?
先ほどのprototypeとも関係しています。
プロトタイプチェーンとはJavaScriptの独自拡張である__proto__プロパティを再帰的に調査し、継承されたprototypeオブジェクトからプロパティを探していく仕組みです。
var hoge = new Hoge(); hoge.__proto__ == Hoge.prototype // true
このコードで注目して頂きたいのはnewをした結果、インスタンスの__proto__とHogeクラスのprototypeが同じ所を指している点です。
これによりhogeインスタンスはHogeクラスの中にあるprototypeオブジェクトのメソッドを参照することが出来ます。
ちなみにnewをすることでしっかりとHogeクラスのprototypeオブジェクトを参照できています。
プロトタイプチェーンでの注意点
しかし、__proto__プロパティの指している参照先を以下のように書き換えるとHogeクラスを参照できなくなり、Hugaクラスのprototyoeを参照することになります。
var hoge = new Hoge(); hoge.__proto__ = Huga.prototype; Huga.apply(hoge);
つまりプロトタイプチェーンという名前ではありますが、実際には__proto__が指し示しているオブジェクトを参照し、その中に探しているプロパティがなく、__proto__プロパティがあれば、更に再帰的に探しに行く、という処理を行っています。
つまり、厳密には必ずしもprototypeオブジェクトを見に行くとは限らないということです。
しかし、一般的に__proto__の中身を弄るようなことはないのであまり気にしなくてもいいのですが、頭の片隅には置いておいたほうがいいと思います。
次回
次回は残りの部分を説明していきたいと思います。
やはり、JavaScriptは今まで触ってきた言語の中でもかなり奥深いですね。
普通に触っている分にはあまり気にならないのですが、
オブジェクト指向を組み込もうとすると途端に難易度があがり、
言語仕様をしっかりと理解していないと引っかかる部分が多いですね。
では、次回まで頑張って勉強しておきます!