私は誰かを怒らせるつもりはありませんが、この種のことは本当に注目に値するものではありません. ブラウザー間の速度の違いのほとんどは、JS エンジンによるものです。V8 エンジンは、たとえばメモリ管理に非常に優れています。特に古い IE の JScript エンジンと比較すると。
次の点を考慮してください。
var closure = (function()
{
var closureVar = 'foo',
someVar = 'bar',
returnObject = {publicProp: 'foobar'};
returnObject.getClosureVar = function()
{
return closureVar;
};
return returnObject;
}());
前回チェックしたとき、クロムは実際には GC'ed でした。これは、FF と Opera の両方が関数スコープ全体をメモリに保持していたのに対しsomeVar
、IIFE の戻り値によって参照されていなかったためです ( によって参照されます)。
このスニペットでは、それは実際には問題ではありませんが、数千行のコードで構成されるモジュール パターン (私の知る限り、ほとんどすべてです) を使用して記述されたライブラリの場合、違いが生じる可能性があります。closure
いずれにせよ、最新の JS エンジンは、単に「ばかげた」解析と実行を行うだけのものではありません。あなたが言ったように、JIT コンパイルが行われていますが、コードを可能な限り最適化するための多くの策略も含まれています。あなたが投稿したスニペットは、FF のエンジンが好きなように書かれている可能性が非常に高いです。
また、どちらが最速のエンジンを搭載しているかについて、Chrome と FF の間で何らかの速度競争が行われていることを覚えておくことも非常に重要です。前回チェックしたとき、Mozilla の Rhino エンジンは Google の V8 よりも優れていると言われていましたが、それが今日でも当てはまるかどうかはわかりません...それ以来、Google と Mozilla の両方がエンジンの開発に取り組んでいます...
結論: さまざまなブラウザ間に速度の違いが存在します。それを否定することはできませんが、1 つの違いは取るに足らないものです。1 つのことだけを何度も実行するスクリプトを作成することは決してありません。重要なのは全体的なパフォーマンスです。
JS もベンチマークするのが難しいバグであることを覚えておく必要があります。コンソールを開いて再帰関数を記述し、FF と Chrome で 100 回実行するだけです。各再帰にかかる時間と全体の実行時間を比較します。次に、数時間待ってからもう一度試してください... FFがトップになる場合もあれば、Chromeの方が速い場合もあります. 私はこの機能でそれを試しました:
var bench = (function()
{
var mark = {start: [new Date()],
end: [undefined]},
i = 0,
rec = function(n)
{
return +(n === 1) || rec(n%2 ? n*3+1 : n/2);
//^^ Unmaintainable, but fun code ^^\\
};
while(i++ < 100)
{//new date at start, call recursive function, new date at end of recursion
mark.start[i] = new Date();
rec(1000);
mark.end[i] = new Date();
}
mark.end[0] = new Date();//after 100 rec calls, first element of start array vs first of end array
return mark;
}());
ここで、最初の質問に戻ります。
$.extend
最初に: あなたが提供したスニペットは、たとえば jQuery のメソッドとはまったく比較になりません:ディープ クローニングは言うまでもなく、実際のクローニングは行われていません。私が調べた他のほとんどのライブラリは循環参照をまったくチェックしません。循環参照をチェックするとプロセス全体が遅くなりますが、ときどき役に立ちます (以下の例 1)。パフォーマンスの違いの一部は、このコードが単に実行することが少ないため、必要な時間が少ないという事実によって説明できます。
第二に、コンストラクターの宣言 (クラスは JS に存在しません) とインスタンスの作成は、実際には 2 つの異なることです (ただし、コンストラクターの宣言自体は、オブジェクトのFunction
インスタンス (正確にはインスタンス) を作成することです)。以下の例 2 に示すように、コンストラクタを記述することで大きな違いが生じる可能性があります. 繰り返しますが、これは一般化であり、特定のエンジンの特定のユースケースには当てはまらない場合があります.インスタンス、たとえその関数がコンストラクターの一部であっても - またはそう言われています。
第三に、あなたが言及したように、長いプロトタイプチェーンをたどることは、あなたが思うほど珍しいことではなく、実際にはそうではありません。例 3 に示すように、2 つまたは 3 つのプロトタイプのチェーンを常にトラバースしています。JS が関数呼び出しを解決したり、式を解決したりする方法に固有のものであるため、これで速度が低下することはありません。
最後に: おそらく JIT コンパイルされていますが、他のライブラリは JIT コンパイルされていないと言うのは通用しません。彼らはそうかもしれませんし、そうでないかもしれません。前に述べたように、異なるエンジンは、あるタスクでは他のタスクよりも優れたパフォーマンスを発揮します... FF JIT コンパイルがこのコードを実行し、他のエンジンは実行しない場合があります。
他のライブラリが JIT コンパイルされない主な理由は、循環参照のチェック、ディープ クローン機能、依存関係 (つまりextend
、さまざまな理由でメソッドがあらゆる場所で使用されている) です。
例 1:
var shallowCloneCircular = function(obj)
{//clone object, check for circular references
function F(){};
var clone, prop;
F.prototype = obj;
clone = new F();
for (prop in obj)
{//only copy properties, inherent to instance, rely on prototype-chain for all others
if (obj.hasOwnProperty(prop))
{//the ternary deals with circular references
clone[prop] = obj[prop] === obj ? clone : obj[prop];//if property is reference to self, make clone reference clone, not the original object!
}
}
return clone;
};
この関数は、オブジェクトの最初のレベルを複製します。元のオブジェクトのプロパティによって参照されているすべてのオブジェクトは引き続き共有されます。簡単な修正は、上記の関数を単純に再帰的に呼び出すことですが、すべてのレベルで循環参照という厄介な問題に対処する必要があります。
var circulars = {foo: bar};
circulars.circ1 = circulars;//simple circular reference, we can deal with this
circulars.mess = {gotcha: circulars};//circulars.mess.gotcha ==> circular reference, too
circulars.messier = {messiest: circulars.mess};//oh dear, this is hell
もちろん、これは最も一般的な状況ではありませんが、防御的にコードを書きたい場合は、多くの人が常に狂ったコードを書いているという事実を認めなければなりません...
例 2:
function CleanConstructor()
{};
CleanConstructor.prototype.method1 = function()
{
//do stuff...
};
var foo = new CleanConstructor(),
bar = new CleanConstructor);
console.log(foo === bar);//false, we have two separate instances
console.log(foo.method1 === bar.method1);//true: the function-object, referenced by method1 has only been created once.
//as opposed to:
function MessyConstructor()
{
this.method1 = function()
{//do stuff
};
}
var foo = new MessyConstructor(),
bar = new MessyConstructor();
console.log(foo === bar);//false, as before
console.log(foo.method1 === bar.method1);//false! for each instance, a new function object is constructed, too: bad performance!
理論的には、最初のコンストラクターの宣言は面倒な方法よりも遅くなります: によって参照される関数オブジェクトはmethod1
、単一のインスタンスが作成される前に作成されます。method1
2 番目の例では、コンストラクターが呼び出される場合を除き、は作成されません。しかし、欠点は非常に大きいnew
です。最初の例のキーワードを忘れると、返される値はundefined
. キーワードを省略すると、2 番目のコンストラクターはグローバル関数オブジェクトをnew
作成し、もちろん呼び出しごとに新しい関数オブジェクトを作成します。実際にはアイドリングしているコンストラクター (およびプロトタイプ) があります...これにより、例 3が表示されます。
例 3:
var foo = [];//create an array - empty
console.log(foo[123]);//logs undefined.
では、舞台裏で何が起こっているか: object、 のインスタンスをfoo
参照し、 Object プロトタイプから継承します ( を試してみてください)。したがって、当然のことながら、Array インスタンスは他のオブジェクトとほとんど同じように機能します。Array
Object.getPrototypeOf(Array.prototype)
foo[123] ===> JS checks instance for property 123 (which is coerced to string BTW)
|| --> property not found @instance, check prototype (Array.prototype)
===========> Array.prototype.123 could not be found, check prototype
||
==========> Object.prototype.123: not found check prototype?
||
=======>prototype is null, return undefined
言い換えれば、あなたが説明するようなチェーンは、あまりにも大げさでも珍しいものでもありません. これが JS のしくみです。つまり、物事を遅くすることを期待することは、脳が弱ってしまうことを期待するようなものです。そうです、考えすぎると疲れてしまう可能性がありますが、いつ休憩するかを知っているからです。プロトタイプ チェーンの場合と同じように: それらは素晴らしいですが、少し遅いことを知っておいてください。はい...