こんにちは、友達!
「情報システムのセキュリティ」コース
が開始
されました。これに関連して、記事「JavaScriptエンジンの基礎:プロトタイプの最適化」の最後の部分を共有します。
また、現在の出版物は次の2つの記事の続きであることを思い出してください。
「JavaScriptエンジンの基礎:一般的なフォームとインラインキャッシュ。 パート1 " 、
" JavaScriptエンジンの基本:一般的なフォームとインラインキャッシュ。 パート2」クラスとプロトタイププログラミングJavaScriptオブジェクトのプロパティにすばやくアクセスする方法がわかったので、JavaScriptクラスのより複雑な構造を見てみましょう。 JavaScriptのクラス構文は次のようになります。
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } }
これはJavaScriptの比較的新しい概念のように見えますが、JavaScriptで常に使用されているプロトタイププログラミングの「構文糖」にすぎません。
function Bar(x) { this.x = x; } Bar.prototype.getX = function getX() { return this.x; };
ここでは、
getX
プロパティを
getX
オブジェクトに
getX
ます。 JavaScriptのプロトタイプは同じオブジェクトであるため、これは他のオブジェクトと同じように機能します。 JavaScriptなどのプロトタイププログラミング言語では、メソッドはプロトタイプを介してアクセスされますが、フィールドは特定のインスタンスに格納されます。
foo
と呼ぶ
Bar
新しいインスタンスを作成するとどうなるかを詳しく見てみましょう。
const foo = new Bar(true);
このコードを使用して作成されたインスタンスには、単一の
'x'
プロパティを持つフォームがあります。 プロトタイプ
foo
は、クラス
Bar
属する
Bar.prototype
です。
この
Bar.prototype
はそれ自体の形式を持ち、プロパティ
'getX'
のみを含み、その値は関数
'getX'
によって決定され、呼び出されると
this.x
返します
this.x
プロトタイプ
Bar.prototype
は、JavaScript言語の一部である
Object.prototype
です。
Object.prototype
はプロトタイプツリーのルートですが、そのプロトタイプは
null
です。
同じクラスの新しいインスタンスを作成すると、すでに理解したように、両方のインスタンスのフォームは同じになります。 両方のインスタンスは、同じ
Bar.prototype
オブジェクトを
Bar.prototype
ます。
プロトタイプのプロパティにアクセスするさて、クラスを定義して新しいインスタンスを作成するとどうなるかがわかりました。 しかし、次の例のように、インスタンスでメソッドを呼び出すとどうなりますか?
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const x = foo.getX();
メソッド呼び出しは、2つの個別のステップとして考えることができます。
const x = foo.getX();
最初のステップはメソッドをロードすることです。これは実際にはプロトタイプのプロパティです(値は関数です)。 2番目のステップは、たとえば
this
の値などのインスタンスを使用して関数を呼び出す
this
です。
foo
インスタンスから
getX
メソッドが
getX
れる最初のステップを詳しく見てみましょう。
エンジンは
foo
インスタンスを開始し、フォーム
foo
に
'getX'
がないことを認識するため、プロトタイプチェーンを介してそれを見つける必要があります。
Bar.prototype
し、プロトタイプフォームを見て、ゼロオフセットに
'getX'
プロパティがあることを確認します。
Bar.prototype
でこのオフセットの値を探し、探していた
JSFunction getX
を見つけます。
JavaScriptの柔軟性により、プロトタイプチェーンリンクを変更できます。次に例を示します。
const foo = new Bar(true); foo.getX();
この例では、
foo.getX()
2回ですが、毎回完全に異なる意味と結果を持っています。 プロトタイプはJavaScriptの単なるオブジェクトであるという事実にもかかわらず、プロトタイプのプロパティへのアクセスを高速化することは、通常のオブジェクトのプロパティへの独自のアクセスを高速化するよりもJavaScriptエンジンにとってさらに重要なタスクです。
日常のプラクティスでは、プロトタイププロパティの読み込みはかなり一般的な操作です。これはメソッドを呼び出すたびに発生します。
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const x = foo.getX();
前に、フォームとインラインキャッシュを使用して、エンジンが通常のプロパティの読み込みを最適化する方法について説明しました。 同じ形状のオブジェクトのプロトタイププロパティの読み込みを最適化するにはどうすればよいですか? 上記では、プロパティがどのようにロードされるかを見ました。
この特定のケースで繰り返しダウンロードしてこれを迅速に行うには、次の3つのことを知る必要があります。
- フォーム
foo
は'getX'
が含まれておらず、変更されていません。 これは、プロパティを追加または削除したり、プロパティの属性の1つを変更したりして、fooオブジェクトを誰も変更していないことを意味します。 - fooプロトタイプは、元の
Bar.prototype
です。 そのため、 Object.setPrototypeOf()
を使用してプロトタイプfoo
を変更したり、特別な_proto_
プロパティに割り当てたりする人はい_proto_
。 Bar.prototype
フォームには'getX'
が含まれており、変更されていません。 つまり、プロパティを追加または削除したり、プロパティ属性の1つを変更したりして、 Bar.prototype
を変更した人はいません。
一般的な場合、これは、インスタンス自体の1つのチェックと、目的のプロパティを含むプロトタイプまでの各プロトタイプに対してさらに2つのチェックを行う必要があることを意味します。 1 + 2Nチェック(Nは使用されたプロトタイプの数)は、プロトタイプチェーンが比較的浅いため、この場合はそれほど悪くはありません。 ただし、通常のDOMクラスの場合のように、エンジンは多くの場合、より長いプロトタイプチェーンを処理する必要があります。 例:
const anchor = document.createElement('a');
HTMLAnchorElement
あり、
getAttribute()
メソッドを呼び出します。 この単純な要素のチェーンには、すでに6つのプロトタイプが含まれています! 興味のあるDOMメソッドのほとんどは、
HTMLAnchorElement
プロトタイプ
HTMLAnchorElement
ではなく、チェーンのどこかにあります。
getAttribute()
メソッドは
Element.prototype
ます。 つまり、
anchor.getAttribute()
を呼び出す
anchor.getAttribute()
に、JavaScriptエンジンは次を必要とします。
'getAttribute'
それ自体anchor
オブジェクトで'getAttribute'
ないことを確認してください。- 最終プロトタイプが
HTMLAnchorElement.prototype
あることを確認します。 - そこに
'getAttribute'
がないことを確認します。 - 次のプロトタイプが
HTMLElement.prototype
あることを確認しHTMLElement.prototype
。 'getAttribute'
が存在しないことを'getAttribute'
確認'getAttribute'
ます。- 次のプロトタイプが
Element.prototype
あることを確認します。 'getAttribute'
れていることを確認してください。
合計7回のチェック。 このタイプのコードはWeb上で非常に一般的であるため、エンジンはさまざまなトリックを使用して、プロトタイププロパティの読み込みに必要なチェックの数を減らします。
foo
'getX'
を要求するときに3つのチェックのみを行った以前の例に戻ります。
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const $getX = foo.getX;
目的のプロパティを含むプロトタイプの前に発生する各オブジェクトについて、このプロパティが存在しないことをフォームで確認する必要があります。 プロトタイプチェックをプロパティの不在のチェックとして提示することにより、チェックの数を減らすことができればいいと思います。 本質的に、これはまさにエンジンが単純なトリックで行うことです。インスタンス自体へのプロトタイプリンクを保存する代わりに、エンジンはそれをフォームに保存します。
各フォームはプロトタイプを示しています。 これは、プロトタイプ
foo
が変更されるたびに、エンジンが新しいフォームに移動することを意味します。 次に、特定のプロパティが存在しないことを確認するためにオブジェクトの形状のみをチェックし、プロトタイプリンクを保護する(プロトタイプリンクを保護する)必要があります。
このアプローチにより、必要なチェックの数を2N + 1から1 + Nに減らして、アクセスを高速化できます。 チェーン内のプロトタイプの数の線形関数であるため、これは依然としてかなり高価な操作です。 エンジンはさまざまなトリックを使用して、特に同じプロパティの順次読み込みの場合に、チェックの数を特定の一定値にさらに削減します。
有効性セルV8は、この目的専用のプロトタイプフォームを処理します。 各プロトタイプには、他のオブジェクト(特に、他のプロトタイプ)と共有されない一意のフォームがあり、これらの各プロトタイプフォームには、それに関連付けられた特別な
ValidityCell
があります。
この
ValidityCell
、関連するプロトタイプまたはその上の他のプロトタイプを誰かが変更するたびに無効になります。 仕組みを見てみましょう。
後続のプロトタイプのダウンロードを高速化するために、V8はインラインキャッシュを4フィールドの場所に配置します。
コードが最初に実行されるときにインラインキャッシュが加熱されると、V8は、プロトタイプ、このプロトタイプ(
Bar.prototype
)、インスタンスフォーム(この場合は
foo
)でプロパティが見つかったオフセットを記憶し、現在の
ValidityCell
を受け取ったプロトタイプにバインドしますフォームのインスタンスから(この場合、
Bar.prototype
が取得されます)。
次回インラインキャッシュを使用するとき、エンジンはインスタンスフォームと
ValidityCell
を確認する必要があります。 それでも有効な場合、エンジンはプロトタイプのオフセットを直接使用し、追加の検索ステップをスキップします。
プロトタイプが変更されると、新しいフォームが強調表示され、以前の
ValidityCell
セルが無効になります。 このため、インラインキャッシュは次回の起動時にスキップされ、パフォーマンスが低下します。
DOM要素を使用した例に戻りましょう。
Object.prototype
各変更は、
Object.prototype
のインラインキャッシュを無効にするだけでなく、
EventTarget.prototype
、
Node.prototype
、
Element.prototype
など、
HTMLAnchorElement.prototype
自体までを含む、その下のチェーン内のプロトタイプのインラインキャッシュも無効にします。
実際、コードの実行中に
Object.prototype
を変更すると、パフォーマンスが
Object.prototype
低下します。 これをしないでください!
これがどのように機能するかをよりよく理解するために、特定の例を見てみましょう。
Bar
クラスと、
Bar
型のオブジェクトのメソッドを呼び出す
loadX
関数があるとします。 同じクラスのインスタンスで
loadX
関数を数回呼び出します。
class Bar { } function loadX(bar) { return bar.getX();
loadX
のインラインキャッシュは、
loadX
ValidityCell
を指すように
Bar.prototype
。 次に、JavaScriptのすべてのプロトタイプのルートである(
Object.prototype
)
Object.prototype
変更すると、
ValidityCell
が無効になり、既存のインラインキャッシュが次回使用されなくなり、パフォーマンスが低下します。
Object.prototype
変更すると、変更時にロードされたプロトタイプのインラインキャッシュが無効になるため、常に悪い考えです。 禁止事項の例を次に示します。
Object.prototype.foo = function() { };
この時点でエンジンによってロードされたすべてのインラインプロトタイプキャッシュを無効にする
Object.prototype
を拡張します。 次に、説明したメソッドを使用するコードを実行します。 エンジンは最初から開始し、プロトタイププロパティへのアクセスのためにインラインキャッシュを構成する必要があります。 そして最後に、以前に追加したプロトタイプメソッドを「クリーンアップ」して削除します。
掃除は良いアイデアだと思いますか? さて、この場合、状況はさらに悪化します! プロパティを削除すると
Object.prototype
が変更さ
Object.prototype
ため、すべてのインラインキャッシュが再び無効になり、エンジンは最初から作業を再開する必要があります。
まとめると 。 プロトタイプは単なるオブジェクトであるという事実にもかかわらず、プロトタイプによるメソッド検索のパフォーマンスを最適化するために、JavaScriptエンジンによって特別に処理されます。
プロトタイプはそのままにしてください! または、本当にそれらを処理する必要がある場合は、コードを実行する前に実行してください。そうすれば、実行中にコードを最適化するすべての試みを少なくとも無効にしないでください。
要約する
JavaScriptがオブジェクトとクラスを保存する方法、およびフォーム、インラインキャッシュ、有効性セルがプロトタイプ操作を最適化する方法を学びました。 この知識に基づいて、実用的な観点からパフォーマンスを改善する方法を理解しました。プロトタイプに手を触れないでください! (または本当に必要な場合は、コードを実行する前に実行してください)。
←
前半この一連の出版物は役に立ちましたか? コメントを書いてください。