はじめに
JS の OOP は好きではありませんでした。学習段階ではあまり使う機会もなく、JS の OOP は中途半端に感じられていました。おそらく Java に先に触れたため、JS の OO 部分に多少の抵抗感があったのでしょう。
偏見は偏見として、面接官が JS の OOP について質問してくるということは、確かに役立つものがあるはずです。偏見を捨てて、真剣に理解するべきでしょう。
約束
P.S. 以下に続くのはやや長い物語になりますので、事前の共通言語を約束しておきます:
/*
* 約束
*/
function Fun(){
// プライベート属性
var val = 1; // プライベート基本属性
var arr = [1]; // プライベート参照属性
function fun(){} // プライベート関数(参照属性)
// インスタンス属性
this.val = 1; // インスタンス基本属性
this.arr = [1]; // インスタンス参照属性
this.fun = function(){}; // インスタンス関数(参照属性)
}
// プロトタイプ属性
Fun.prototype.val = 1; // プロトタイプ基本属性
Fun.prototype.arr = [1]; // プロトタイプ参照属性
Fun.prototype.fun = function(){}; // プロトタイプ関数(参照属性)
上記の約束は比較的合理的なものだと思います。理解しにくい場合は、[黯羽軽揚:JS 学習ノート 2_オブジェクト指向](http://ayqy.net/blog/JS 学习笔记 2_面向对象/) を参照して、より多くの基本常識を理解してください。
一. 単純プロトタイプチェーン
これは継承を実装する最もシンプルな方法です。本当に超シンプルで、核心は一言だけです(コード中にコメントで明記しました)
1. 具体的な実装
function Super(){
this.val = 1;
this.arr = [1];
}
function Sub(){
// ...
}
Sub.prototype = new Super(); // 核心
var sub1 = new Sub();
var sub2 = new Sub();
sub1.val = 2;
sub1.arr.push(2);
alert(sub1.val); // 2
alert(sub2.val); // 1
alert(sub1.arr); // 1, 2
alert(sub2.arr); // 1, 2
2. 核心
親クラスのインスタンスをサブクラスのプロトタイプオブジェクトとして使用する
3. 長所と短所
長所:
- シンプルで実装が容易
短所:
- sub1.arr を変更すると sub2.arr も変化する。プロトタイプオブジェクトからの参照属性はすべてのインスタンスで共有されるため。
このように理解できます:sub1.arr.push(2); を実行すると、まず sub1 に対して属性検索を行います。インスタンス属性をくまなく探します(本例ではインスタンス属性がありません)。見つからない場合、プロトタイプチェーンを辿って sub1 のプロトタイプオブジェクトに到達し、そこに arr 属性が見つかります。そこで arr の末尾に 2 を挿入するため、sub2.arr も変化します。
- サブクラスのインスタンスを作成する際、親クラスのコンストラクタに引数を渡せない
二. コンストラクタ借用
単純プロトタイプチェーンは確かにシンプルですが、2 つの致命的な欠点があり実用できません。そこで 20 世紀末の jsers はこの 2 つの欠点を修正する方法を考え出し、コンストラクタ借用方式が生まれました。
1. 具体的な実装
function Super(val){
this.val = val;
this.arr = [1];
this.fun = function(){
// ...
}
}
function Sub(val){
Super.call(this, val); // 核心
// ...
}
var sub1 = new Sub(1);
var sub2 = new Sub(2);
sub1.arr.push(2);
alert(sub1.val); // 1
alert(sub2.val); // 2
alert(sub1.arr); // 1, 2
alert(sub2.arr); // 1
alert(sub1.fun === sub2.fun); // false
2. 核心
親クラスのコンストラクタを借用してサブクラスインスタンスを強化する。つまり、親クラスのインスタンス属性をサブクラスインスタンスにコピーして装着するのです(プロトタイプは全く使用しません)
3. 長所と短所
長所:
-
サブクラスインスタンスが親クラスの参照属性を共有する問題を解決
-
サブクラスインスタンスを作成する際、親クラスのコンストラクタに引数を渡せる
P.S. 先人たちはこんなに効率的で、2 つの欠点を瞬時に修復しました
短所:
- 関数の再利用が実現できない。各サブクラスインスタンスが新しい fun 関数を持っているため、多くなるとパフォーマンスに影響し、メモリが爆発します。。
P.S. さて、参照属性の共有問題を修復したばかりなのに、またこの新しい問題が発生しました。。
三. 組み合わせ継承(最も一般的)
現在のコンストラクタ借用方式にはまだ問題があります(関数の再利用が実現できない)。大丈夫、继续して修復します。jsers は苦労して組み合わせ継承を生み出しました。
1. 具体的な実装
function Super(){
// ここでのみ基本属性と参照属性を宣言
this.val = 1;
this.arr = [1];
}
// ここで関数を宣言
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
Super.call(this); // 核心
// ...
}
Sub.prototype = new Super(); // 核心
var sub1 = new Sub(1);
var sub2 = new Sub(2);
alert(sub1.fun === sub2.fun); // true
2. 核心
インスタンス関数はすべてプロトタイプオブジェクト上に配置して関数の再利用を実現する。同時にコンストラクタ借用方式の長所も保持する。Super.call(this); を通じて親クラスの基本属性と参照属性を継承し、引数を渡せる長所を保持。Sub.prototype = new Super(); を通じて親クラスの関数を継承し、関数の再利用を実現。
3. 長所と短所
長所:
- 参照属性の共有問題が存在しない
- 引数を渡せる
- 関数を再利用できる
短所:
- (少しの欠陥)サブクラスのプロトタイプ上に余分な親クラスインスタンス属性が存在する。親クラスのコンストラクタが 2 回呼び出され、2 つ生成されたため。サブクラスインスタンス上のものがサブクラスプロトタイプ上のものを遮蔽している。。。これもメモリの無駄遣いで、先ほどの状況よりはマシですが、確かに欠陥です。
P.S. この「余分」が理解できない場合は、[黯羽軽揚:JS 学習ノート 2_オブジェクト指向](http://ayqy.net/blog/JS 学习笔记 2_面向对象/) を参照してください。記事の末尾により詳細な説明があります。
四. パラサイティック組み合わせ継承(最良の方法)
名前からも分かるように、これも組み合わせ継承の最適化です。組み合わせ継承に欠陥があると言いましたが、大丈夫、完璧を追求し続けます。
1. 具体的な実装
function beget(obj){ // 子供を産む関数 beget:龍は龍を beget、鳳は鳳を beget。
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
// ここでのみ基本属性と参照属性を宣言
this.val = 1;
this.arr = [1];
}
// ここで関数を宣言
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
Super.call(this); // 核心
// ...
}
var proto = beget(Super.prototype); // 核心
proto.constructor = Sub; // 核心
Sub.prototype = proto; // 核心
var sub = new Sub();
alert(sub.val);
alert(sub.arr);
P.S. ちょっと待って、子供を産む関数って何ですか?聞いたことがない。それに核心と明記された 3 つの文も、なぜ理解できないのか?焦らないでください。お茶を飲んでから続きを見ましょう。
2. 核心
beget(Super.prototype); を使用してプロトタイプオブジェクト上の余分な親クラスインスタンス属性を切り離す
P.S. 何?理解できない?おお~、プロトタイプ式継承とパラサイティック継承を紹介するのを忘れていました。ドアの鍵を閉め忘れたと言っていたのは。。この記憶力。
P.S. パラサイティック組み合わせ継承、この名前はあまり適切ではありません。パラサイティック継承との関係は特に大きくありません。
3. 長所と短所
長所:完璧です
短所:理論的にはありません(使用するのが面倒なことを短所としない場合。。)
P.S. 使用するのが面倒なことは一方面です。另一方面、パラサイティック組み合わせ継承は比較的遅く出現したもので、21 世紀初頭のものです。みんなそんなに長く待てなかったので、組み合わせ継承が最も一般的に使用されています。この理論的に完璧な方案は教科書上の最良の方法に過ぎません。
五. プロトタイプ式
実際、上記の完璧な方案を紹介すれば終了できますが、組み合わせ継承から完璧な方案への思考の飛躍がかなりあるように思えます。物語を明確にする必要があります。
1. 具体的な実装
function beget(obj){ // 子供を産む関数 beget:龍は龍を beget、鳳は鳳を beget。
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
// 親クラスオブジェクトを取得
var sup = new Super();
// 子供を産む
var sub = beget(sup); // 核心
// 強化
sub.attr1 = 1;
sub.attr2 = 2;
//sub.attr3...
alert(sub.val); // 1
alert(sub.arr); // 1
alert(sub.attr1); // 1
P.S. ほら~見えましたか、子供を産む関数 beget が現れました。
2. 核心
子供を産む関数を使用して*「純潔」な新しいオブジェクトを取得する(「純潔」なのはインスタンス属性がないため)。その後徐々に強化する(インスタンス属性を充填する)*
P.S. ES5 は Object.create() 関数を提供しており、内部はプロトタイプ式継承です。IE9+ がサポート。
3. 長所と短所
長所:
- 既存のオブジェクトから新しいオブジェクトを派生させる。カスタムタイプを作成する必要がない(継承というよりはオブジェクトコピーのようです。。)
短所:
-
プロトタイプの参照属性はすべてのインスタンスで共有される。親クラスオブジェクト全体をサブクラスのプロトタイプオブジェクトとして使用するため、この欠陥は避けられない。
-
コードの再利用が実現できない(新しいオブジェクトは即座に取得し、属性はその場で追加する。関数でカプセル化されていないため、どうやって再利用できるのか)
P.S. これって継承と大きな関係があるのでしょうか?なぜニコラスもこれを実装継承の 1 つの方法として挙げているのでしょうか?大きな関係はありませんが、一定の関係はあります。
六. パラサイティック式
この名前はあまりにも変です。しかも*パラサイティックは一種のパターン(套路)*で、継承の実装にしか使えないわけではありません。
1. 具体的な実装
function beget(obj){ // 子供を産む関数 beget:龍は龍を beget、鳳は鳳を beget。
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
function getSubObject(obj){
// 新しいオブジェクトを作成
var clone = beget(obj); // 核心
// 強化
clone.attr1 = 1;
clone.attr2 = 2;
//clone.attr3...
return clone;
}
var sub = getSubObject(new Super());
alert(sub.val); // 1
alert(sub.arr); // 1
alert(sub.attr1); // 1
2. 核心
プロトタイプ式継承にマントを着せただけ。継承のように見えるようになりました(上記で紹介したプロトタイプ式継承はオブジェクトコピーのようです)
注意:beget 関数は必須ではありません。つまり、新しいオブジェクトを作成 -> 強化 -> そのオブジェクトを返す、というプロセスをパラサイティック継承と呼びます。新しいオブジェクトがどのように作成されるかは重要ではありません(beget で産まれたもの、new で作成されたもの、リテラルで即座に作られたもの。。すべて可)
3. 長所と短所
長所:
- やはりカスタムタイプを作成する必要がない
短所:
- 関数の再利用が実現できない(プロトタイプを使用していないため、もちろん不可)
P.S. ストーリー解析:欠陥のあるパラサイティック継承 + 完璧ではない組み合わせ継承 = 完璧なパラサイティック組み合わせ継承。戻って探して みてください。どこでパラサイティックが使用されているか。
七. 6 つの継承方法の関連性
P.S. 破線は補助的な作用を表し、実線は決定的な作用を表します。
参考資料
-
『JavaScript 高級程序设计』
-
『JavaScript 语言精粹』

コメントはまだありません