web-dev-qa-db-ja.com

JavaScriptプロトタイプシステムは、古典的なクラスシステムを模倣する以外に何ができるでしょうか?

プロトタイプシステムは、従来のクラスシステムよりもはるかに柔軟に見えますが、人々は、従来のクラスシステムを模倣したいわゆる「ベストプラクティス」に満足しているようです。

function foo() {
  // define instance properties here
}

foo.prototype.method = //define instance method here

new foo()

プロトタイプシステムがすべての柔軟性で実行できる他のことがあるに違いありません。

クラスを模倣する以外に、プロトタイプシステムの用途はありますか?プロトタイプができること、クラスができないこと、またはないことは何ですか?

65
gsklee

プロトタイプシステムは、標準オブジェクトを介して継承を実装することにより、 メタプログラミング の魅力的なモデルを提供します。もちろん、これは主にインスタンスのクラスの確立された単純な概念を表現するために使用されますが、クラスを作成するために特定の構文を必要とする言語レベルの不変の構造としてのクラスはありません。プレーンオブジェクトを使用することで、オブジェクトに対して実行できるすべてのこと(およびすべてを実行できること)が「クラス」に対して実行できるようになります。これが、あなたが話している柔軟性です。

この柔軟性は、JavaScriptの特定のオブジェクト変更機能のみを使用して、プログラムでクラスを拡張および変更するために多く使用されます。

  • 多重継承のためのミックスインと特性
  • プロトタイプは、それらから継承するオブジェクトがインスタンス化された後に変更できます
  • 高階関数とメソッドデコレータは、プロトタイプの作成に簡単に使用できます

もちろん、プロトタイプモデル自体は、単にクラスを実装するよりも強力です。クラスの概念は非常に有用で広く普及しているため、これらの機能はほとんど使用されません。そのため、プロトタイプの継承の実際の能力はよく知られていません(JSエンジンでは十分に最適化されていません:-/)

  • 既存のオブジェクトのプロトタイプを切り替えることで、それらの動作を劇的に変えることができます。 (フルサポートは ES6 Reflect.setPrototypeOf
  • いくつかのソフトウェアエンジニアリングパターンは、オブジェクトを使用して直接実装できます。例としては、 フライウェイトパターン プロパティ付き、 責任の連鎖 動的連鎖を含む、ああ、そしてもちろん プロトタイプパターン があります。

    最後のものの良い例は、デフォルトのオプションオブジェクトです。誰もがそれらを使用して作成します

    var myOptions = extend({}, defaultOptions, optionArgument);
    

    しかし、より動的なアプローチは、

    var myOptions = extend(Object.create(defaultOptions), optionArgument);
    
46
Bergi

2013年6月に、私は 古典的なものに対するプロトタイプの継承の利点 に関する質問に答えました。それ以来、私はプロトタイプと古典の両方の継承について熟考することに多くの時間を費やし、 プロトタイプ-クラス同型 について広範囲に書いた。

はい、プロトタイプ継承の主な用途はクラスをシミュレートすることです。ただし、クラスをシミュレートするだけでなく、さまざまな用途に使用できます。たとえば、プロトタイプチェーンはスコープチェーンと非常によく似ています。

プロトタイプスコープの同型写像も

JavaScriptのプロトタイプとスコープには多くの共通点があります。 JavaScriptには3つの一般的なタイプのチェーンがあります。

  1. プロトタイプチェーン。

    _var foo = {};
    var bar = Object.create(foo);
    var baz = Object.create(bar);
    
    // chain: baz -> bar -> foo -> Object.prototype -> null
    _
  2. スコープチェーン。

    _function foo() {
        function bar() {
            function baz() {
                // chain: baz -> bar -> foo -> global
            }
        }
    }
    _
  3. メソッドチェーン。

    _var chain = {
        foo: function () {
            return this;
        },
        bar: function () {
            return this;
        },
        baz: function () {
            return this;
        }
    };
    
    chain.foo().bar().baz();
    _

3つのうち、プロトタイプチェーンとスコープチェーンが最も類似しています。実際、 notoriouswith ステートメントを使用して、プロトタイプチェーンをスコープチェーンにアタッチできます。

_function foo() {
    var bar = {};
    var baz = Object.create(bar);

    with (baz) {
        // chain: baz -> bar -> Object.prototype -> foo -> global
    }
}
_

では、プロトタイプスコープの同型の使用は何ですか?直接的な使用法の1つは、プロトタイプチェーンを使用してスコープチェーンをモデル化することです。これは、私がJavaScriptで実装した自分のプログラミング言語 Bianca に対して行ったこととまったく同じです。

私は最初にBiancaのグローバルスコープを定義し、次のように適切な名前のファイルに一連の便利な数学関数を入力しました: global.js

_var global = module.exports = Object.create(null);

global.abs   = new Native(Math.abs);
global.acos  = new Native(Math.acos);
global.asin  = new Native(Math.asin);
global.atan  = new Native(Math.atan);
global.ceil  = new Native(Math.ceil);
global.cos   = new Native(Math.cos);
global.exp   = new Native(Math.exp);
global.floor = new Native(Math.floor);
global.log   = new Native(Math.log);
global.max   = new Native(Math.max);
global.min   = new Native(Math.min);
global.pow   = new Native(Math.pow);
global.round = new Native(Math.round);
global.sin   = new Native(Math.sin);
global.sqrt  = new Native(Math.sqrt);
global.tan   = new Native(Math.tan);

global.max.rest = { type: "number" };
global.min.rest = { type: "number" };

global.sizeof = {
    result: { type: "number" },
    type: "function",
    funct: sizeof,
    params: [{
        type: "array",
        dimensions: []
    }]
};

function Native(funct) {
    this.funct = funct;
    this.type = "function";
    var length = funct.length;
    var params = this.params = [];
    this.result = { type: "number" };
    while (length--) params.Push({ type: "number" });
}

function sizeof(array) {
    return array.length;
}
_

Object.create(null)を使用してグローバルスコープを作成したことに注意してください。グローバルスコープには親スコープがないため、これを行いました。

その後、プログラムごとに、プログラムのトップレベルの定義を保持する個別のプログラムスコープを作成しました。コードは analyzer.js という名前のファイルに保存されています。このファイルは大きすぎて1つの答えに収まりません。ファイルの最初の3行は次のとおりです。

_var parse = require("./ast");
var global = require("./global");
var program = Object.create(global);
_

ご覧のとおり、グローバルスコープはプログラムスコープの親です。したがって、programglobalを継承し、スコープ変数のルックアップをオブジェクトプロパティのルックアップと同じくらい簡単にします。これにより、言語の実行時間がはるかに簡単になります。

プログラムスコープには、プログラムの最上位の定義が含まれています。たとえば、 matrix.bianca ファイルに格納されている次の行列乗算プログラムについて考えてみます。

_col(a[3][3], b[3][3], i, j)
    if (j >= 3) a
    a[i][j] += b[i][j]
    col(a, b, i, j + 1)

row(a[3][3], b[3][3], i)
    if (i >= 3) a
    a = col(a, b, i, 0)
    row(a, b, i + 1)

add(a[3][3], b[3][3])
    row(a, b, 0)
_

最上位の定義は、colrow、およびaddです。これらの各関数には、プログラムスコープから継承する独自の関数スコープもあります。そのためのコードは analyzer.jsの67行目 にあります:

_scope = Object.create(program);
_

たとえば、addの関数スコープには、行列aおよびbの定義があります。

したがって、クラスのほかに、プロトタイプは関数スコープのモデリングにも役立ちます。

代数的データ型をモデル化するためのプロトタイプ

利用可能な抽象化のタイプはクラスだけではありません。関数型プログラミング言語では、データは 代数的データ型 を使用してモデル化されます。

代数的データ型の最良の例は、リストの例です。

_data List a = Nil | Cons a (List a)
_

このデータ定義は、aのリストが空のリスト(つまり、Nil)であるか、またはaのリストに挿入されたタイプ「a」の値(つまり、Cons a (List a))である可能性があることを意味します。たとえば、以下はすべてリストです。

_Nil                          :: List a
Cons 1 Nil                   :: List Number
Cons 1 (Cons 2 Nil)          :: List Number
Cons 1 (Cons 2 (Cons 3 Nil)) :: List Number
_

データ定義の型変数aは、 パラメトリック多態性 を有効にします(つまり、リストが任意の型の値を保持できるようにします)。たとえば、Nilのタイプは_List a_であり、aは何でもかまいません。これは、数値のリストまたはブール値のリストに特化できます。

これにより、lengthのようなパラメトリック関数を作成できます。

_length :: List a -> Number
length Nil        = 0
length (Cons _ l) = 1 + length l
_

length関数は単にリストの値を気にしないため、リストに含まれる値のタイプに関係なく、リストの長さを見つけるためにlength関数を使用できます。

パラメトリック多相性に加えて、ほとんどの関数型プログラミング言語には、なんらかの形の アドホック多相性 もあります。アドホック多相性では、多相変数のタイプに応じて、関数の1つの特定の実装が選択されます。

たとえば、JavaScriptの_+_演算子は、引数のタイプに応じて、加算と文字列の連結の両方に使用されます。これは、アドホック多相性の一形態です。

同様に、関数型プログラミング言語では、通常、map関数はオーバーロードされます。たとえば、リスト用のmapの異なる実装、セット用の異なる実装などがある場合があります。型クラスは、アドホック多相性を実装する1つの方法です。たとえば、 Functor 型クラスはmap関数を提供します。

_class Functor f where
    map :: (a -> b) -> f a -> f b
_

次に、さまざまなデータ型に対してFunctorの特定のインスタンスを作成します。

_instance Functor List where
    map :: (a -> b) -> List a -> List b
    map _ Nil        = Nil
    map f (Cons a l) = Cons (f a) (map f l)
_

JavaScriptのプロトタイプを使用すると、代数的データ型とアドホック多相性の両方をモデル化できます。たとえば、上記のコードは次のように1対1でJavaScriptに変換できます。

_var list = Cons(1, Cons(2, Cons(3, Nil)));

alert("length: " + length(list));

function square(n) {
    return n * n;
}

var result = list.map(square);

alert(JSON.stringify(result, null, 4));_
_<script>
// data List a = Nil | Cons a (List a)

function List(constructor) {
    Object.defineProperty(this, "constructor", {
        value: constructor || this
    });
}

var Nil = new List;

function Cons(head, tail) {
    var cons  = new List(Cons);
    cons.head = head;
    cons.tail = tail;
    return cons;
}

// parametric polymorphism

function length(a) {
    switch (a.constructor) {
    case Nil:  return 0;
    case Cons: return 1 + length(a.tail);
    }
}

// ad-hoc polymorphism

List.prototype.map = function (f) {
    switch (this.constructor) {
    case Nil:  return Nil;
    case Cons: return Cons(f(this.head), this.tail.map(f));
    }
};
</script>_

クラスを使用してアドホック多相性をモデル化することもできますが、オーバーロードされたすべての関数を1か所で定義する必要があります。プロトタイプを使用すると、どこにでもそれらを定義できます。

結論

ご覧のとおり、プロトタイプは非常に用途が広いです。はい、主にクラスのモデル化に使用されます。ただし、他の多くの用途に使用できます。

プロトタイプを使用できるその他のいくつかのこと:

  1. 作成 永続データ構造 構造共有あり。

    構造共有の基本的な考え方は、オブジェクトを変更する代わりに、元のオブジェクトから継承する新しいオブジェクトを作成し、必要に応じて変更を加えることです。プロトタイプの継承はその点で優れています。

  2. 他の人が述べたように、プロトタイプは動的です。したがって、新しいプロトタイプメソッドをさかのぼって追加することができ、それらはプロトタイプのすべてのインスタンスで自動的に使用可能になります。

お役に立てれば。

32
Aadit M Shah

プロトタイプの継承システムでは、メソッド/プロパティをより動的に追加できると思います。

他の人が書いたクラスを簡単に拡張できます。たとえば、すべてのjQueryプラグインがあります。また、ネイティブクラスに簡単に追加したり、文字列や配列などにユーティリティ関数を追加したりすることもできます。

例:

// I can just add whatever I want to anything I want, whenever I want
String.prototype.first = function(){ return this[0]; };

'Hello'.first() // == 'H'

他のクラスからメソッドをコピーすることもできます。

function myString(){
  this[0] = '42';
}
myString.prototype = String.prototype;

foo = new myString();
foo.first() // == '42'

また、プロトタイプを拡張できることも意味しますafterオブジェクトはそれから継承しましたが、それらの変更は適用されます。

そして、個人的には、プロトタイプは本当に便利でシンプルだと思います。オブジェクト内にメソッドを配置することは、私にとって本当に魅力的です;)

11
theonlygusti

JavaScriptには、そのようなクラスの概念はありません。ここではすべてがオブジェクトです。そして、JavaScriptのすべてのオブジェクトは Object から派生しています。プロトタイププロパティは、オブジェクト指向の方法でアプリケーションを開発しているときに、継承に役立ちます。プロトタイプには、従来のオブジェクト指向構造のクラスよりも多くの機能があります。

プロトタイプでは、他の誰かによって書かれた関数にプロパティを追加できます。

例:.

Array.prototype.print=function(){
  console.log(this);
}

継承での使用:

プロトタイププロパティを使用して継承を使用できます。 ここ はJavaScriptで継承を使用する方法です。

従来のクラスシステムでは、クラスが定義されると変更できません。しかし、あなたはプロトタイプシステムでJavaScriptで行うことができます。

0
Laxmikant Dange