質問 では、jQueryとネイティブJSが互いにどのように機能するかについて説明しました。
もちろん、Vanillaソリューションはアレイ全体を処理しないため、はるかに高速に実行できますが、Array.filter
私はかなり自信があると少なくとも$.grep
。
驚いたことに、テストに追加した後、レッスンを教えられました:Testsuite
エッジケース もちろん異なる結果になります。
_$.grep
は、ネイティブメソッドの3倍以上高速になるはずですArrray.filter
?
編集: MDNからのフィルターシム を使用するようにテストを変更し、結果はかなり興味深いものになりました。
そして、最終的に私がそれを見てみたいと思っていたような結果
ECMAScript 5.1仕様のセクション15.4.4.2 は、次のようにArray.prototype.filter(callbackfn, thisArg)
を定義します。
callbackfn
は、3つの引数を受け入れ、ブール値true
またはfalse
に強制可能な値を返す関数でなければなりません。filter
は、配列の各要素に対して昇順でcallbackfn
を1回呼び出し、callbackfn
がtrue
を返すすべての値の新しい配列を作成します。callbackfn
は、実際に存在する配列の要素に対してのみ呼び出されます。配列の欠落要素については呼び出されません。
thisArg
パラメーターが指定されている場合、this
の呼び出しごとにcallbackfn
値として使用されます。指定されていない場合は、代わりにundefined
が使用されます。
callbackfn
は、要素の値、要素のインデックス、およびトラバースされるオブジェクトの3つの引数で呼び出されます。
filter
は、呼び出されるオブジェクトを直接変更しませんが、callbackfn
の呼び出しによってオブジェクトが変更される場合があります。Filterによって処理される要素の範囲は、
callbackfn
の最初の呼び出しの前に設定されます。 filterの呼び出しが開始された後に配列に追加される要素には、callbackfn
がアクセスしません。配列の既存の要素が変更された場合、callbackfn
に渡される値は、フィルターがそれらを訪問したときの値になります。 filterの呼び出しが開始されてからアクセスされる前に削除される要素はアクセスされません。
それ自体はすでに多くの作業です。 ECMAScriptエンジンが実行する必要がある多くのステップ。
次に、次のことを言い続けます。
1つまたは2つの引数を使用してフィルターメソッドが呼び出されると、次の手順が実行されます。
O
をToObject
を引数として渡してthis
を呼び出した結果とします。lenValue
を、O
の_[[Get]]
_内部メソッドを引数length
で呼び出した結果とします。len
をToUint32(lenValue)
とする。 IsCallable(callbackfn)がfalseの場合、TypeError例外をスローします。 thisArgが指定された場合、TをthisArgにします。それ以外の場合は、Tを未定義にします。 Aを式new Array()によって作成された新しい配列とします。ここで、Arrayはその名前を持つ標準の組み込みコンストラクタです。 kを0にします。0にします。k<lenの間、繰り返します。PkをToString(k)にします。 kPresentを、引数Pkを使用してOの[[HasProperty]]内部メソッドを呼び出した結果とします。 kPresentがtrueの場合、kValueを引数PkでOの[[Get]]内部メソッドを呼び出した結果とします。 selectedを、コールバックfnの[[Call]]内部メソッドをTとしてthis値と、kValue、k、およびOを含む引数リストで呼び出した結果とします。ToBoolean(selected)がtrueの場合、[[DefineOwnProperty]]引数ToString(to)、プロパティ記述子{[[Value]]:kValue、[[Writable]]:true、[[Enumerable]]:true、[[Configurable]]:true}、およびfalseを持つAの内部メソッド。に1を増やします。kを1増やします。Aを返します。フィルターメソッドの長さプロパティは1です。
注フィルター機能は意図的に汎用です。 this値がArrayオブジェクトである必要はありません。したがって、メソッドとして使用するために他の種類のオブジェクトに転送できます。フィルター関数をHostオブジェクトに正常に適用できるかどうかは、実装に依存します。
このアルゴリズムに関する注意事項:
多くの場合、これらはどれも必要ありません。そのため、独自のfilter
メソッドを記述する場合、ほとんどの場合、これらの手順を実行する手間さえかかりません。
ES5.1準拠のJavaScriptエンジンはすべて、そのアルゴリズムに準拠する必要があるため、_Array#filter
_を使用するたびにこれらすべての手順を実行する必要があります。
これらの手順の一部のみを実行するカスタム作成メソッドが高速になることは驚くべきことではありません:)
独自のfilter
関数を作成する場合、上記のアルゴリズムほど複雑ではない可能性があります。おそらく、配列をオブジェクトに変換することはまったくありません。ユースケースによっては、配列をフィルタリングするだけでは必要ない場合があるためです。
面白いことがわかりました。 MarcoKで説明したように、$。grepはforループを使用した単純な実装です。ほとんどの場合、フィルターは低速であるため、実装は異なる必要があります。私は答えを見つけたと思います:
function seak (e) { return e === 3; }
var array = [1,2,3,4,5,6,7,8,9,0], i, before;
array[10000] = 20; // This makes it slow, $.grep now has to iterate 10000 times.
before = new Date();
// Perform natively a couple of times.
for(i=0;i<10000;i++){
array.filter(seak);
}
document.write('<div>took: ' + (new Date() - before) + '</div>'); // took: 8515 ms (8s)
before = new Date();
// Perform with JQuery a couple of times
for(i=0;i<10000;i++){
$.grep(array, seak);
}
document.write('<div>took: ' + (new Date() - before) + '</div>'); // took: 51790 ms (51s)
この場合、ネイティブの「フィルター」ははるかに高速です。したがって、配列インデックスではなくプロパティを反復処理すると思います。
ここで、「大きな」問題に戻りましょう;)。
スクリプトは間違っていませんか?
にとって array.filter
1000回測定を行っており、合計を1000で除算して表示します。
にとって JQuery.grep
1回測定を行っており、合計を1000で割って表示します。
これは、grepが実際に比較に使用する値の1000倍遅いことを意味します。
Firefoxのクイックテストは以下を提供します。
Machine 1:
average filter - 3.864
average grep - 4.472
Machine2:
average filter - 1.086
average grep - 1.314
chromeでのクイックテストは以下を提供します:
Machine 1:
average filter - 69.095
average grep - 34.077
Machine2:
average filter - 18.726
average grep - 9.163
Firefox(50.0)の結論は、コードパスに対して非常に高速であり、フィルターはjquery.grepよりも約10〜15%高速です。
Chromeはコードパスに対して非常に遅いですが、grepはarray.filterよりも50%高速であるようで、firefoxの実行よりも900%遅くなっています。