まず、他の言語とは異なり、JS の効率は JS エンジンの効率に大きく依存します。エンジン実装の長所と短所に加えて、エンジン自体もいくつかの特殊なコード パターンに対していくつかの最適化戦略を採用します。たとえば、FF、Opera、Safari の JS エンジンは、文字列連結操作 (+) を特別に最適化しています。明らかに、最大の効率を達成するには、エンジンの気質を理解し、エンジンの好みに応えるように努める必要があります。したがって、異なるエンジンでは、行われる最適化は互いに矛盾する可能性が最も高くなります。
そして、クロスブラウザー Web プログラミングを行う場合、最大の問題は IE6 (JScript 5.6) です。 hotfix がないと、JScript エンジンのガベージ コレクションのバグにより、実際のアプリケーションでのパフォーマンスが他のブラウザーと比べて桁違いに低くなってしまうためです。したがって、この状況での最適化は、実際には JScript の最適化となります。
したがって、最初の原則は、IE6 (パッチを適用していない JScript 5.6 以前) に対してのみ最適化することです。
プログラムが IE6 で許容可能なパフォーマンスに最適化されている場合、基本的に他のブラウザーでのパフォーマンスに問題はありません。
したがって、以下で説明する問題の多くは、他のエンジンではまったく異なる可能性があることに注意してください。たとえば、ループ内の文字列の結合には通常 Array.join が必要であると考えられていますが、これは「The +」が原因です。操作が最適化されたため、Array.join の使用は「+」を直接使用する場合ほど効率的ではありません。しかし、IE6 を考慮すると、他のブラウザーとのこの効率の差はまったく言及する価値がありません。
JS の最適化は、他の言語の最適化と同じです。たとえば、最適化を開始してすぐに急いで実行しないでください。それは意味がありません。最適化の鍵は、依然として最も重要な場所、つまりボトルネックに焦点を当てることです。一般に、大規模なループではボトルネックが必ず発生します。これは、ループ自体にパフォーマンスの問題があるということではありませんが、ループによって潜在的なパフォーマンスの問題が急速に増幅される可能性があるということです。
したがって、2 番目の原則は、大規模なループを主な最適化オブジェクトとして使用することです。
以下の最適化原則は、大規模なループでのみ意味があります。ループ本体の外側でこのような最適化を行うことは、基本的には意味がありません。
現状、ほとんどのJSエンジンはインタープリター実行されており、インタープリター実行の場合、どの操作においても関数呼び出しの効率が悪くなります。さらに、過度に深いプロトタイプ継承チェーンやマルチレベル参照も効率を低下させます。 JScript では、レベル 10 参照のオーバーヘッドは、空の関数呼び出しのオーバーヘッドの約 1/2 です。どちらのオーバーヘッドも単純な演算 (四則演算など) よりもはるかに大きくなります。
したがって、3 番目の原則は、多すぎる参照レベルと不必要な複数のメソッド呼び出しを避けるように努めることです。
場合によっては、プロパティへのアクセスのように見えるものが、実際にはメソッド呼び出しであることに注意することが重要です。たとえば、すべての DOM プロパティは実際にはメソッドです。 NodeList を走査するとき、ループ条件による nodes.length へのアクセスは属性の読み取りのように見えますが、実際には関数呼び出しと同等です。さらに、IE DOM の実装では、childNodes.length は毎回内部トラバーサルによって再カウントされる必要があります。 (なんと、これは本当です! childNodes.length のアクセス時間は childNodes.length の値に比例することが測定されているからです!) これは非常に高価です。したがって、事前にnodes.lengthをjs変数に保存しておくと、トラバーサルのパフォーマンスが確実に向上します。
これも関数呼び出しであり、ユーザー定義関数の効率は言語組み込み関数の効率よりもはるかに低くなります。後者はエンジンのネイティブ メソッドのラッパーであり、エンジンは通常 C で書かれているためです。 C++、および Java。さらに、同じ関数の場合、組み込み言語構造のコストは通常、組み込み関数呼び出しよりも効率的です。これは、前者は JS コードの解析段階で決定および最適化できるためです。
したがって、4 番目の原則は、言語自体の構造と組み込み関数を使用しようとすることです。
これは、高性能 String.format メソッド の例です。 String.format の従来の実装では、String.replace(regex, func) を使用します。 pattern に n 個のプレースホルダー (繰り返しのものを含む) が含まれる場合、カスタム関数 func が n 回呼び出されます。この高パフォーマンスの実装では、各形式呼び出しは Array.join 操作と String.replace(regex, string) 操作のみを実行し、どちらもカスタム関数呼び出しを行わずにエンジンの組み込みメソッドです。 2 つの組み込みメソッド呼び出しと n 回のカスタム メソッド呼び出し、これがパフォーマンスの違いです。
これも組み込み機能ですが、それでもパフォーマンスに違いがあります。たとえば、JScript の引数へのアクセス パフォーマンスは非常に低く、関数呼び出しにほぼ追いつきます。したがって、可変パラメータを持つ単純な関数がパフォーマンスのボトルネックになる場合は、内部変更を加えて引数にアクセスせず、パラメータを明示的に判断することで処理できます。
例:
Java コード
function sum() { var r = 0; for (var i = 0; i < arguments.length; i++) { r += arguments[i]; } return r; }
この合計は通常、小さな数値で呼び出され、パラメーターが少ない場合のパフォーマンスの向上が期待されます。
Java コード
function sum() { switch (arguments.length) { case 1: return arguments[0]; case 2: return arguments[0] + arguments[1]; case 3: return arguments[0] + arguments[1] + arguments[2]; case 4: return arguments[0] + arguments[1] + arguments[2] + arguments[3]; default: var r = 0; for (var i = 0; i < arguments.length; i++) { r += arguments[i]; } return r; } }
に変更すると、実際にはあまり改善されませんが、次のように変更すると:
Java代码
function sum(a, b, c, d, e, f, g) { var r = a ? b ? c ? d ? e ? f ? a + b + c + d + e + f : a + b + c + d + e : a + b + c + d : a + b + c : a + b : a : 0; if (g === undefined) return r; for (var i = 6; i < arguments.length; i++) { r += arguments[i]; } return r; }
就会提高很多(至少快1倍)。
最后是第五原则,也往往是真实应用中最重要的性能障碍,那就是尽量减少不必要的对象创建。
本身创建对象是有一定的代价的,但是这个代价其实并不大。最根本的问题是由于JScript愚蠢之极的垃圾回收调度算法,导致随着对象个数的增加,性能严重下降(据微软的人自己说复杂度是O(n^2))。
比如我们常见的字符串拼接问题,经过我的测试验证,单纯的多次创建字符串对象其实根本不是性能差的原因。要命的是在对象创建期间的无谓的垃圾回收的开销。而Array.join的方式,不会创建中间字符串对象,因此就减少了那该死的垃圾回收的开销。
因此,如果我们能把大规模对象创建转化为单一语句,则其性能会得到极大的提高!例如通过构造代码然后eval——实际上PIES项目中正在根据这个想法来做一个专门的大规模对象产生器……
好了上面就是总结的JS优化五大原则。
以上がJavaScript の 5 つの主要な最適化原則を要約するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。