web-dev-qa-db-ja.com

プロトタイプを介してメソッドを定義するか、コンストラクターでこれを使用するか-本当にパフォーマンスの違い?

JavaScriptでは、「クラス」を作成してパブリック関数を提供する方法が2つあります。

方法1:

function MyClass() {
    var privateInstanceVariable = 'foo';
    this.myFunc = function() { alert(privateInstanceVariable ); }
}

方法2:

function MyClass() { }

MyClass.prototype.myFunc = function() { 
    alert("I can't use private instance variables. :("); 
}

私は何度も読んだことがあります 言って すべてのインスタンスがそれぞれの関数を取得するのではなく、すべてのインスタンスが同じ関数のコピーを共有するため、方法2を使用する方が効率的です。ただし、プロトタイプを介して関数を定義することには大きなデメリットがあります。プライベートインスタンス変数を持つことは不可能です。

理論的には、方法1を使用すると、オブジェクトの各インスタンスに関数の独自のコピーが提供されます(したがって、割り当てに必要な時間は言うまでもなく、より多くのメモリを使用します)。最適化Webブラウザーがこの非常に一般的なパターンを認識し、実際にオブジェクト参照のすべてのインスタンスを同じ関数のコピーにすることで簡単に作成できるようですこれらの「コンストラクター関数」を介して定義されます。その後、後で明示的に変更された場合にのみ、インスタンスに関数の独自のコピーを与えることができます。

両者のパフォーマンスの違いについての洞察-または、より良い実世界の経験-は、非常に役立ちます。

60
MgSam

http://jsperf.com/prototype-vs-this を参照してください

プロトタイプを介してメソッドを宣言する方が高速ですが、これが適切かどうかは議論の余地があります。

アプリにパフォーマンスのボトルネックがある場合、たとえば、任意のアニメーションのすべてのステップで10000以上のオブジェクトをインスタンス化していない限り、これは起こりそうにありません。

パフォーマンスが深刻な問題であり、マイクロ最適化を行う場合は、プロトタイプを使用して宣言することをお勧めします。それ以外の場合は、最も意味のあるパターンを使用してください。

JavaScriptには、プライベートとして表示されることを意図したプロパティに下線を付けるという慣例があります(例:_process())。ほとんどの開発者は、社会契約を放棄するつもりがない限り、これらのプロパティを理解して回避しますが、その場合は、それらに対応しないこともできます。つまり、おそらくtrueプライベート変数は必要ないでしょう...

61
James

Chromeの新しいバージョンでは、this.methodはprototype.methodよりも約20%高速ですが、新しいオブジェクトの作成にはまだ時間がかかります。

常に新しいオブジェクトを作成する代わりにオブジェクトを再利用できる場合、これは新しいオブジェクトを作成するよりも50%から90%速くなります。さらに、ガベージコレクションを行わないことのメリットは非常に大きくなります。

http://jsperf.com/prototype-vs-this/59

2
Yongtao Wang

多くのインスタンスを作成する場合にのみ、違いが生じます。それ以外の場合、メンバー関数を呼び出すパフォーマンスはどちらの場合でもまったく同じです。

これを実証するために、jsperfでテストケースを作成しました。

http://jsperf.com/prototype-vs-this/1

1
typeracer

あなたはこれを考慮していなかったかもしれませんが、オブジェクトに直接メソッドを置くことは実際には一つの方法でより良いです:

  1. メソッド呼び出しは非常にわずかです高速ですjsperf )プロトタイプチェーンなのでメソッドを解決するために相談する必要はありません。

ただし、速度の違いはほとんど無視できます。その上、プロトタイプにメソッドを配置することは、さらに2つの影響力のある方法で優れています。

  1. インスタンス作成の高速化jsperf
  2. より少ないメモリを使用します

Jamesが言ったように、クラスの数千のインスタンスをインスタンス化する場合、この違いは重要になる可能性があります。

つまり、各オブジェクトにアタッチしている関数がインスタンス間で変更されないことを認識し、すべてのインスタンスメソッドが共有関数を指すように、メモリに関数の1つのコピーのみを保持するJavaScriptエンジンを想像できます。実際、Firefoxはこのような特別な最適化を行っているようですが、Chromeはそうではありません。


ASIDE:

プロトタイプの内部メソッドからプライベートインスタンス変数にアクセスすることは不可能です。それで、あなたがあなた自身に尋ねなければならない質問は、継承とプロトタイピングの利用よりもインスタンス変数を本当にプライベートにすることができることを評価することでしょうか?私は個人的に、変数を本当にプライベートにすることはそれほど重要ではなく、アンダースコアプレフィックス(たとえば、「this._myVar」)を使用して、変数はパブリックですがプライベートであると見なされる必要があることを示します。とはいえ、ES6では、両方の世界の両方を持つ方法が明らかにあります。

1
Niko Bellic

このアプローチを使用すると、prototypeを使用してインスタンス変数にアクセスできるようになります。

var Person = (function () {
    function Person(age, name) {
        this.age = age;
        this.name = name;
    }

    Person.prototype.showDetails = function () {
        alert('Age: ' + this.age + ' Name: ' + this.name);
    };

    return Person; // This is not referencing `var Person` but the Person function

}()); // See Note1 below

注1:

括弧は関数(自己呼び出し関数)を呼び出し、その結果をvar Personに割り当てます。


使用方法

var p1 = new Person(40, 'George');
var p2 = new Person(55, 'Jerry');
p1.showDetails();
p2.showDetails();
0
CodingYoshi

この回答は、不足しているポイントを埋める残りの回答の拡張と見なす必要があります。個人的な経験とベンチマークの両方が組み込まれています。

私の経験では、メソッドがプライベートであるかどうかに関係なく、コンストラクターを使用して文字通りオブジェクトを忠実に構築しています。主な理由は、私が始めたときそれが私への最も簡単な即時のアプローチであったので、それは特別な好みではないということです。それは私が目に見えるカプセル化が好きで、プロトタイプが少し具体化されているのと同じくらい単純だったかもしれません。私のプライベートメソッドもスコープ内の変数として割り当てられます。これは私の習慣であり、物事をうまく自己完結させていますが、常に最良の習慣であるとは限らず、壁にぶつかることもあります。構成オブジェクトとコードレイアウトに従って非常に動的な自己アセンブルを行う奇抜なシナリオは別として、特にパフォーマンスが懸念される場合、それは私の意見では弱いアプローチになる傾向があります。内部がプライベートであることを知ることは有用ですが、適切な規律で他の手段を介してそれを達成できます。パフォーマンスが重要な考慮事項でない限り、当面のタスクにはそれ以外の場合に最適に機能するものを使用してください。

  1. プロトタイプ継承と規則を使用してアイテムをプライベートとしてマークすると、コンソールまたはデバッガーからオブジェクトグラフを簡単にトラバースできるため、デバッグが容易になります。一方、このような規則は難読化をいくらか難しくし、他の人が自分のスクリプトをサイトにボルトオンするのを容易にします。これは、プライベートスコープアプローチが人気を得た理由の1つです。これは本当のセキュリティではなく、抵抗を追加します。残念ながら、多くの人々はまだそれが安全なJavaScriptをプログラミングする真の方法であると考えています。デバッガーは本当によくなったので、コードの難読化が行われます。クライアントに多すぎるセキュリティの欠陥を探しているなら、それはあなたが見たいかもしれないデザインパターンです。
  2. 慣例により、ほとんど手間をかけずにプロパティを保護できます。それは祝福と呪いになり得ます。制限が少ないため、継承の問題が緩和されます。プロパティにアクセスする可能性のある他の場所を検討する際に、衝突または認知負荷の増加のリスクがまだあります。自己組み立てオブジェクトを使用すると、多くの継承の問題を回避できる奇妙なことができますが、従来とは異なる場合があります。私のモジュールは、機能が他の場所で必要になる(共有される)か、外部で必要とされない限り公開されるまで、物事が引き出されない豊富な内部構造を持つ傾向があります。コンストラクターパターンは、単なる断片的なオブジェクトよりも、自己完結型の洗練されたモジュールの作成につながる傾向があります。あなたがそれを望むなら、それは大丈夫です。それ以外の場合、より伝統的なOOP=構造とレイアウトが必要な場合は、慣例によりアクセスを規制することをお勧めします。私の使用シナリオでは、複雑なOOPはしばしば正当化されませんとモジュールがトリックを行います。
  3. ここでのテストはすべて最小限のものです。実際の使用では、モジュールがより複雑になり、ここでのテストが示すよりもはるかにヒット数が大きくなる可能性があります。複数のメソッドを処理するプライベート変数を持つことは非常に一般的であり、それらのメソッドのそれぞれは、プロトタイプ継承では得られない初期化のオーバーヘッドを追加します。累積的に加算される可能性がありますが、そのようなオブジェクトの少数のインスタンスのみが浮かんでいるため、ほとんどの場合問題ではありません。
  4. プロトタイプのルックアップのために、プロトタイプメソッドの呼び出しが遅くなるという仮定があります。それは不当な仮定ではありません。テストするまで私も同じことをしました。実際にはそれは複雑であり、いくつかのテストはアスペクトが取るに足らないことを示唆しています。の間に、 prototype.m = fthis.m = fおよびthis.m = function...後者は、同じように機能する最初の2つよりも大幅に優れています。プロトタイプルックアップだけが重要な問題である場合、最後の2つの関数が最初の関数を大幅に実行します。代わりに、奇妙な何かが少なくともカナリアが関係しているところで起こっています。関数がメンバーであるものに従って最適化される可能性があります。パフォーマンスに関する多くの考慮事項が関係しています。パラメータアクセスと変数アクセスにも違いがあります。
  5. 記憶容量。ここでは十分に議論されていません。真実である可能性が高いと前もって判断できる仮定は、プロトタイプ継承は通常、はるかにメモリ効率がよく、私のテストによると、それは一般的であるということです。コンストラクターでオブジェクトを構築する場合、各オブジェクトはおそらく共有されるのではなく、各関数の独自のインスタンスを持っていると想定できます。独自の個人プロパティの大きなプロパティマップと、コンストラクタースコープを開いたままにしておくためのオーバーヘッドが考えられます。プライベートスコープで動作する関数は、メモリを非常に過度に要求します。多くのシナリオでは、メモリの比例差はCPUサイクルの比例差よりもはるかに重要であることがわかりました。
  6. メモリグラフ。エンジンを詰まらせてGCをより高価にすることもできます。プロファイラーは、最近GCで費やされた時間を示す傾向があります。それ以上の割り当てと解放に関しては、問題だけではありません。トラバースする大きなオブジェクトグラフなどを作成して、GCがより多くのサイクルを消費するようにします。 100万個のオブジェクトを作成し、それらにほとんど触れない場合、エンジンによっては、予想以上に周囲のパフォーマンスに影響を与える可能性があります。これにより、オブジェクトが破棄されるときに、少なくともgcが長く実行されることが証明されています。つまり、使用されるメモリとGCにかかる時間には相関がある傾向があります。ただし、メモリに関係なく同じ時刻になる場合があります。これは、グラフの構成(間接層、アイテム数など)の影響が大きいことを示しています。これは常に予測が容易なことではありません。
  7. 多くの人がチェーンプロトタイプを広範囲に使用しているわけではありません。プロトタイプチェーンは、理論的には高価になる可能性があります。誰かがしますが、私はコストを測定していません。代わりに、完全にコンストラクターでオブジェクトを構築し、各コンストラクターがそれ自体で親コンストラクターを呼び出すときに継承のチェーンがある場合、理論的にはメソッドアクセスがはるかに高速になります。逆に、プロトタイプを祖先チェーンにフラット化するなど、同等のことが可能で、本当に必要な場合は、instanceofなどのhasOwnPropertyなどを壊してもかまいません。どちらの場合も、パフォーマンスハックに関してこの道を進むと、状況は複雑になります。たぶん、やってはいけないことをしてしまうでしょう。
  8. 多くの人々は、あなたが提示したどちらのアプローチも直接使用しません。代わりに、匿名オブジェクトを使用して独自の方法で作成し、どの方法でもメソッドを共有できます(たとえば、ミックスイン)。モジュールとオブジェクトを整理するための独自の戦略を実装するフレームワークもいくつかあります。これらは、慣習に基づくカスタムアプローチです。ほとんどの人にとって、そしてあなたにとってあなたの最初の挑戦はパフォーマンスではなく組織であるべきです。これは、Javascriptが、より明示的なOOP /名前空間/モジュールのサポートを備えた言語やプラットフォームと比較して多くの方法を実現するという点で、しばしば複雑です。パフォーマンスに関しては、何よりもまず大きな落とし穴を避けるために言います。
  9. プライベート変数とメソッドで機能するはずの新しいSymbol型があります。これを使用するにはいくつかの方法があり、パフォーマンスとアクセスに関連する多くの質問が生じます。私のテストでは、Symbolsのパフォーマンスは他のすべてに比べて優れていませんでしたが、徹底的にテストしたことはありません。

免責事項:

  1. パフォーマンスについては多くの議論があり、使用シナリオやエンジンが変化するため、これに対する恒久的な正解が常にあるとは限りません。プロファイルは常に正確ですが、常に正確で信頼できるとは限らないため、常に複数の方法で測定します。明らかに実証可能な問題がない限り、最適化への多大な努力を避けてください。
  2. 代わりに、自動テストに機密領域のパフォーマンスチェックを含めて、ブラウザーが更新されたときに実行する方が良いでしょう。
  3. バッテリーの寿命だけでなく、知覚可能なパフォーマンスも重要な場合があります。最も遅いソリューションは、最適化コンパイラーを実行した後、速くなる可能性があります(つまり、コンパイラーは、制限付きスコープ変数がいつ慣例によりプライベートとしてマークされたプロパティよりもアクセスされるかについてより良い考えを持つかもしれません)。 node.jsなどのバックエンドを検討してください。これには、ブラウザでよく見られるよりも優れたレイテンシとスループットが必要になる場合があります。ほとんどの人は、登録フォームの検証のようなものでこれらのことを心配する必要はありませんが、そのようなことが問題になる可能性のある多様なシナリオの数は増えています。
  4. 結果を保持するには、メモリ割り当て追跡ツールに注意する必要があります。データを返して永続化しなかった場合や、完全に最適化された場合や、インスタンス化/非参照の間にサンプルレートが不十分で、配列がどのように初期化され、3.4KiBとして登録された100万個に満たされるかについて頭を悩ませる場合がありました。割り当てプロファイル内。
  5. 現実の世界では、ほとんどの場合、アプリケーションを本当に最適化する唯一の方法は、最初にそれを記述して測定できるようにすることです。どのシナリオでも、数千ではないにしても数十から数百の要素が関与する可能性があります。エンジンは、非対称または非線形のパフォーマンス特性につながる可能性のあることも行います。コンストラクターで関数を定義する場合、それらはアロー関数または従来の関数である可能性があり、特定の状況ではそれぞれが異なる動作をし、他の関数タイプについてはわかりません。また、クラスは、同等のプロトタイプコンストラクターのパフォーマンスと同じようには動作しません。ベンチマークにも十分注意する必要があります。プロトタイプ化されたクラスは、さまざまな方法で初期化を据え置くことができます(特に、プロトタイプがプロパティもプロトタイプ化した場合)。これは、初期化コストとアクセス/プロパティ変更コストを過大評価できることを意味します。また、漸進的な最適化の兆候も見ました。これらの場合、同じ配列のオブジェクトのインスタンスで大きな配列を埋めましたが、インスタンスの数が増えると、オブジェクトは、残りが同じになるまで、メモリに対して段階的に最適化されているように見えます。また、これらの最適化がCPUパフォーマンスに大きな影響を与える可能性もあります。これらは、作成したコードだけでなく、オブジェクトの数、オブジェクト間の差異など、実行時に何が起こるかに大きく依存しています。
0
jgmjgm

つまり、メソッド2を使用して、すべてのインスタンスが共有するプロパティ/メソッドを作成します。これらは「グローバル」になり、変更はすべてのインスタンスに反映されます。インスタンス固有のプロパティ/メソッドを作成するには、メソッド1を使用します。

もっと良いリファレンスがあればいいのですが、とりあえず this を見てください。同じプロジェクトで異なる方法で両方の方法を使用した方法を確認できます。

お役に立てれば。 :)

0
Johnny