理系公務員のプログラミング日記

【JS忍者】プロトタイプによるオブジェクト指向

タグ:
JavaScript

参考書1度読んでも全然分からんこと多いけど、こうやってまとめるとかなり理解できる気がする。

6.1 実体化とプロトタイプ

6.1.1 オブジェクトの実体化

すべての関数はprototypeプロパティを持つが、最初は空のオブジェクトを参照している。 newキーワードを用いて関数がコンストラクタとして呼び出されたとき、新しく実体化された空のオブジェクトが渡される。

同じ関数を関数として呼び出したときと、newキーワードを用いてコンストラクタとして呼び出した時の比較。

空の関数にprototypeの身を設定した時、関数として呼び出すとundefinedが帰ってくるが、 コンストラクタとして呼び出すと、オブジェクトが作成され、さらにそのオブジェクトのプロパティにprotptypeに設定したメソッドが設定される。

function Ninja(){} // 関数のプロトタイプにメソッドを追加する Ninja.prototype.swingSword = () => true; // その関数を「関数として」呼び出す。 何も生じない const ninja1 = Ninja(); // undefined // 同じ関数をコントラクタとして呼び出す。 const ninja2 = new Ninja(); // ninja2 && ninja2.swingSword && ninja2.swingSword() が存在する

コンストラクタ関数の中で、thisパラメータを用いてプロパティの値を初期化することができる。

function Ninja(){ this.swung = false; // インスタンスメソッド this.swingSword = () => !this.swung; }; // インスタンスメソッドと同じ名前のプロトタイプメソッド Ninja.prototype.swingSword = () => this.swung; const ninja = new Ninja(); ninja.swingSword() // True

このことから、まずコンスタラクタのプロトタイプメソッドを利用してインスタンスが作成され、 その後にコンストラクタ関数内部のプロパティがインスタンスに追加されることが分かる。

ちなみに、インスタンスが作成されたときに、プロパティが単純にコピーされるわけではない。 オブジェクトを作成した後でプロトタイプメソッドを追加しても問題はない。 オブジェクトでプロパティ参照を解決するときに、アタッチされているプロトタイプを参照する。

function Ninja(){ this.swung = true; }; const ninja = new Ninja(); // オブジェクトを作成した後で、プロトタイプメソッドを追加 Ninja.prototype.swingSword = () => this.swung; // この時点で初めてアタッチされているプロトタイプを参照する。 ninja.swingSword() // True

6.1.2 コンストラクタ経由で行われるオブジェクトの「型付け」と逆参照

instanceof演算子を使うと、あるインスタンスが特定の関数コンストラクタから作られたかを調べることができる。 constructorプロパティを使うと、コンストラクタへの逆参照(refer back)によって、instanceofと同じようにインスタンスの起源を確認することができる。 動的更新(live-update)と呼ぶらしい

function Ninja(){} const ninja = new Ninja(); typeof ninja === "object" // True // instanceofを使うと型をテストすることができる ninja instanceof Ninja // True // コンストラクタ参照を使ってもninjaの型をテストすることができる。 ninja.constructor == Ninja // True

6.1.3 プロトタイプチェーン

これまではprototypeにメソッドを定義していたが、オブジェクトのインスタンスをprototypeに定義することができる。 これをObjectまで連鎖させることをプロトタイプチェーンに呼び、これによってJavaScriptでも継承を扱うことができる。

function Person(){} Person.prototype.dacne = function(){}; function Ninja(){} // プロトタイプチェーン Ninja.prototype = new Person(); const ninja = new Ninja(); ninja instanceof Ninja // True ninja instanceof Person // True ninja instanceof Object // True

6.1.4 HTML DOM のプロトタイプ

ブラウザでは、すべてのDOM要素がHTMLElementコンストラクタを継承している。 例として,prototypeを用いてremove()というメソッドを実装する。

HTMLElement.prototype.remove = function(){ if (this.parentNode) this.parentNode.removeChild(this); } // 従来の方法 const a = document.getElementById("a"); a.parentNode.removeChild(a); // 実装した方法 document.getElementById("b").remove();

6.2 落とし穴に注意!

6.2.1 オブジェクトの拡張

Object.prototypeは拡張するのは良くない。ありとあらゆるオブジェクトにプロパティが追加されてしまう。

6.2.2 Numberの拡張

Number.prototypeも拡張しない方が良い。数値が変数内にある場合や式の場合は良いが、数値リテラルとして直接扱う場合に問題が起こる。

Number.prototype.add = (num) => this + num; // 1.数値が変数内にある let n = 5; n.add(3) == 8; // True // 2.数値が式の場合 (5).add(3) == 8 // True // 3.数値リテラルの場合 5.add(3) == 8 // Error!

6.2.3 ネイティブオブジェクトを継承する

6.2.4 実体化の問題

関数は「普通の」関数とコンストラクタという2つの役割を演じることができる。 new演算子をつけてコンストラクタとして使うべき関数にnewを付け忘れると、新しいオブジェクトが実体化されない。 それだけでなく、「普通の」関数内では this はグローバルスコープを指すので、場合によってはグローバルスコープの全く関係のない 変数を参照してしまう場合がある。

function User(first, last){ this.name = first + " " + last; } let name = "Rukia"; // コンストラクタを間違って呼び出す let user = User("Ichigo","Kurosaki"); name == "Rukia" // False