@ledsun blog

無味の味は佳境に入らざればすなわち知れず

Douglas Crockford の説明する「JavaScriptのプロトタイプ継承」

Douglas Crockford によるJavaScriptのプロタイプ継承の説明 Prototypal Inheritance の翻訳です。

JavaScriptのプロトタイプ継承 2006-06-07

Douglas Crockford - www.crockford.com

私は五年前に「Classical Inheritance in JavaScript」(中国語 イタリア語 )を書き、JavaScriptがクラスを持たないプロトタイプ言語であること、クラシック*1なシステムを真似れる*2ことを示しました。あれから私のプログラミングスタイルは、良いプログラマの常として、進歩しました。私はプロトタイプ主義*3を完全に受け入れ、クラシックなプログラミングモデルへの拘りを捨てました。

JavaScript自体がプロトタイプの性質に関して矛盾しているため、私は遠回りしました。プロトタイプのシステムではオブジェクトがオブジェクトを継承します。しかし、JavaScriptにはこのための演算子がありません。代わりにnew演算子があります。例えば

new F ()

これは

f.prototype

を継承する新しいオブジェクトを生成します

この不自然さは、クラシックに慣れたプログラマに身近に感じられるよう意図されたものですが、失敗しています。実際、JavaプログラマからのJavaScriptの評価は芳しくありません。JavaScriptコンストラクタパターンは、クラシックなプログラマへのアピールにはなりませんでした。さらに、JavaScriptのプロトタイプの本当の性質を分かりにくくしました。このためほとんどのプログラマは、この言語の有効な使い方知りません。

ラッキーなことに、本当のプロトタイプ継承を実現する演算子は簡単に作れます。これは私のツールキットの標準機能です。とてもお薦めです。

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

object関数は、JavaScriptコンストラクタパターンに代わり、本当のプロトタイプ継承を実現します。引数に古いオブジェクトを取り、そのオブジェクトを継承した新しいオブジェクトを返します。新しいオブジェクトからメンバを取得しようとした際に、そのキーが無い場合は古いオブジェクトのメンバを取得します。オブジェクトがオブジェクトから継承します。これ以上のオブジェクト指向があるでしょうか?

クラスを作る代わりにプロトタイプオブジェクトを作ります。そしてobject関数を使って、新しいインスタンスを作成します。JavaScriptではオブジェクトは変更可能なので、新しいインスタンスに新しいフィールドやメソッドを加えて拡張することができます。新しいインスタンスは、別の新しいオブジェクトのプロトタイプになることもできます。同じオブジェクトをたくさん作るのにクラスは必要ありません。

便宜のため、object関数を呼ぶ関数を作って、特権メソッド*4を持つ新しいオブジェクトを拡張する他のカスタマイズ提供できます。私はこういった関数をmaker関数と呼びます。もし、あるmaker関数がobject関数ではなく別のmakerの関数を呼び出すならば、それはparasitic inheritance pattern*5です。

私はJavaScriptのこれらのツールとラムダとオブジェクトリテラルを組み合わせて使うと、大規模で複雑で効率的な、よく構造化されたプログラムを書けることを発見しました。今、クラシックなオブジェクトモデルが最も人気がありますが、私はプロトタイプオブジェクトモデルがより有能でより表現力を持つと考えています。

また私は、これらの新しいパターンを学んでクラシカルプログラマーとしても成長しました。動的プログラミングの世界で培った洞察は静的な世界でも力を発揮します。

追記 2007-04-02

別解

Object.prototype.begetObject = function () {
    function F() {}
    F.prototype = this;
    return new F();
};

newObject = oldObject.begetObject();

追記その2 2008-04-07

object関数はグローバル関数なのが問題です。Object.prototype.begetObjectには、時に無用なプログラムのbegetObject関数を上書きし予想不可能な影響を引き起こす問題があります。

そこで、今では私はこのスタイルを使っています:

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}
newObject = Object.create(oldObject);

*1:ClassとClassicを掛けています。 JavaScriptパターン の「6.1 クラシカルとモダンの継承パターン対決」に解説が載っています。

*2:コンストラクタパターン。JavaScriptのパターン コンストラクタパターンを見てください。

*3:prototypalism

*4:そのような構文があるわけではなくプライベートな要素を操作する公開関数のこと。JavaScriptパターン の 「5.3.2 特権メソッド」に解説が載っています。または JavaScriptにおけるPrivileged Methods - yinkywebの日記 または、を見てください。

*5:JavaScript: The Good Partsの「5.4 関数パターン」では関数型継承パターンとして紹介されています。またはObject Oriented JavaScript - Inheritance - 10 - orangespiderの日記を見てください。