JavaScriptによるプログラミングに関する世界観3(プロトタイプチェーン)です。
JavaScriptフレームワークも参照のこと。
JavaScriptはプロトタイプベースでオブジェクト指向を行う。
aplly()メソッドやcall()メソッドを使って「委譲」と呼ばれるオブジェクト指向を行うことが大きな特徴。
JavaScriptでは、クラスベースではなく、プロトタイプベースの「委譲」と呼ばれるオブジェクト指向を行う。
上手く使うことで、あるメソッドを任意のオブジェクトに適用して実行したり、あるオブジェクトのメソッドを別のオブジェクトに関連づけさせて実行させることができる(委譲)。場合場合によってさまざまなメソッドをさまざまなオブジェクトに対して実行したり、あとでオブジェクトに対するメソッドを変更・追加したりすることもできる。
プロトタイプ継承とは、そのオブジェクトのメンバが存在しなくても、そのオブジェクトのプロトタイプとされているオブジェクトのメンバが存在すれば、そのメンバを実行するという方法です。
たとえば、CalcオブジェクトのプロトタイプにBasicCalcが存在した場合、Calcオブジェクトにadd()メソッドが存在しなければ、BasicCalcオブジェクトにadd()メソッドがないかどうかを確認し、その結果あった場合はBasicCalcオブジェクトのadd()メソッドを実行します。
プロトタイプ継承は、プロトタイプのプロトタイプのさらにプロトタイプ、のように、鎖で繋がれたチェーンのようにひとつひとつ参照していきます。そのため、「プロトタイプチェーン」と呼ばれます。
後日注記:JavaScriptでは、prototypeに同じプロトタイプを所有することで、オブジェクトの間で共通のインターフェースを持つことができます。
基本的なコード例は、たとえばコンストラクタは、以下のようになる。
function Calc(x, y) { this.x = x; this.y = y; this.a = x + y; this.s = x - y; this.m = x * y; this.d = x / y; }
プロトタイプを以下のように宣言する。
Calc.prototype.printAll = function() { console.log(this.x + " + " + this.y + " = " + this.a); console.log(this.x + " - " + this.y + " = " + this.s); console.log(this.x + " * " + this.y + " = " + this.m); console.log(this.x + " / " + this.y + " = " + this.d); }
そして、以下のようにnewしてオブジェクトを作成する。
let calc_list = [ new Calc(1, 3), new Calc(13, 23), new Calc(16, 15), new Calc(20, 7) ];
最後に、メソッドは以下のように取得できる。
for (const value of calc_list.values()) { value.printAll(); }
まさに、関数(コンストラクタ)のprototypeに関数(メソッド)を追加し、その上でnewしてオブジェクトとメソッドをいっぺんに作り出すという風に、まるでプラモデルを組み立てるようにプログラムの開発をすることができる。
(おうちで学べるプログラミングのきほんを参考にしながら自分でコードを書きました。)
this参照とcall(), apply(), bind()を使うことで、メソッドをその都度異なるオブジェクトに関連付けて実行できる。これを委譲と言う。(コンストラクタをnewした場合は、新しいオブジェクトが作成される。)
call()は、あるオブジェクトにあるメソッドを関連づけて呼び出すことができる。
apply()は、あるオブジェクトにあるメソッドを関連づけて呼び出すことができる。第二引数は配列で渡す。
bind()は、あるメソッドに対してあるオブジェクトをbindで関連づける。
以下は自分の書いたコード。
function calc() { let l = this.x; let r = this.y; let a = l + r; let s = l - r; let m = l * r; let d = l / r; console.log(l + " + " + r + " = " + a); console.log(l + " - " + r + " = " + s); console.log(l + " * " + r + " = " + m); console.log(l + " / " + r + " = " + d); } let v1 = { x : 100, y : 200 }; let v2 = { x : 150, y : 30 }; let v3 = { x : 30, y : 150 }; let v4 = { x : 120, y : 130 }; calc.call(v1); calc.call(v2); calc.call(v3); let func_v4 = calc.bind(v4); func_v4();
JavaScriptのプロトタイプチェーンでは、関数に用意されている「prototype」というプロパティをチェーンのように辿っていって、「プロトタイプ継承」を可能とする。
JavaScriptでは、関数は第一級のオブジェクトであり、関数もオブジェクトのプロパティとして格納できる。関数(コンストラクタ)のプロパティとして関数(メソッド)を格納することもできる。ここで、関数は参照されているだけであり、束縛されない。
JavaScriptのプロトタイプチェーンでは、関数(オブジェクト)のプロパティ(メソッド)を探していく際に、もしそのメソッドがその関数自体になかったとしても、prototypeとして参照されているオブジェクトにそのメソッドがあればそれを参照する。
コンストラクタとなるメソッドのprototypeにメソッドを格納しておき、このprototypeを持ったコンストラクタをnewすると、オブジェクトが新しく生成されるが、このオブジェクトに対して、obj.prototype.meth()とアクセスしなくても、obj.meth()とアクセスできる。
obj.meth()とメソッドにアクセスした場合、obj.meth()があった場合はobj.meth()を参照するが、なかった場合、obj.prototype.meth()があるかどうかを探して、あった場合これを参照する。これもなかった場合はobj.prototype.prototype.meth()を探す。ここで探されていくのは、「暗黙リンク」と呼ばれる参照で、コンストラクタのprototypeからこのように暗黙リンクをひとつひとつ辿っていく。
つまり、直接のプロパティとしてのmeth()がなかったとしても、prototypeとなっているオブジェクトにそのメソッドがあればそれを継承する。これを「プロトタイプ継承」と呼ぶ。
同じメソッド名をもったオブジェクトを量産するには、「同じプロトタイプを所有」すればよい。たとえば、meth()メソッドを持ったobjxがあった場合、objyとobjzはprototypeにobjxを代入すればいい。そうすることで、objyとobjzからobjxのメソッドにアクセスできる。
このようにすることで、prototypeの内容をあらかじめ作っておいてnewすれば、クラスを使うことなしに、オブジェクトの量産が可能となる。Calc.prototype.printAll()メソッドは、コンストラクタであるCalc()を使って作られたオブジェクトobjから、obj.printAll()として実行することができる。
また、このようになっているおかげで、自分で定義したメソッドではなくても、プロトタイプ継承されたメソッドであれば、自分で定義したメソッドを使うのと同様に呼び出すことができる。toString()やvalueOf()のようなメンバは、Object.prototypeで定義されているため、Object.prototypeを継承する多くのインスタンスで実行できる。
以下は参考文献。
オブジェクト指向も参照のこと。
関数型プログラミングも参照のこと。