39

質問では、jQuery とネイティブ JS が互いにどのように機能するかについて議論されました。

もちろん、バニラのソリューションは配列全体を処理しないため、はるかに高速に実行されArray.filterますが、少なくとも$.grep.

驚いたことに、それをテストに追加した後、私はレッスンを教えられました:テストスイート

もちろん、エッジケースの結果は異なります。

$.grepネイティブメソッドよりも 3 倍以上高速である理由を知っている人はいArrray.filterますか?

編集: MDN のフィルター shimを使用するようにテストを変更しました。結果は非常に興味深いものです。

  • Chrome: MDN shim でさえネイティブ メソッドよりも高速で、jQuery の方がはるかに優れています
  • Firefox: shim はネイティブ メソッドよりも少し遅く、jQuery の方がはるかに優れています

そして最終的に、私が期待していたような結果が得られました

  • Internet Explorer: ネイティブ メソッドが最も速く、次に jQuery、shim が最も遅い (おそらくこれは、IE の JS エンジンがかなり弱いためです...)
4

5 に答える 5

17

このブログ投稿にあるように (同じ種類のテストも行っています):

のドキュメントを読めばfilter、なぜこれほど遅いのかがわかります。

  1. 配列内の削除された値とギャップを無視します
  2. オプションで、述語関数の実行コンテキストを設定します
  3. 述語関数がデータを変更するのを防ぎます
于 2013-02-01T13:39:17.557 に答える
8

ECMAScript 5.1 仕様のセクション 15.4.4.20では、次のように定義Array.prototype.filter(callbackfn, thisArg)されています。

callbackfntrueは、3 つの引数を受け取り、ブール値または に強制可能な値を返す関数でなければなりませんfalse。は、配列内の各要素に対して昇順で 1 回filter呼び出し、すべての値の新しい配列を作成します。実際に存在する配列の要素に対してのみ呼び出されます。配列の要素が欠落している場合は呼び出されません。callbackfncallbackfntruecallbackfn

パラメータが指定されている 場合は、 の各呼び出しの値thisArgとして使用されます。指定されていない場合は、 代わりに が使用されます。thiscallbackfnundefined

callbackfn要素の値、要素のインデックス、およびトラバースされるオブジェクトの 3 つの引数で呼び出されます。

filterが呼び出されたオブジェクトを直接変更することはありませんが、オブジェクトは への呼び出しによって変更される場合がありますcallbackfn

フィルタによって処理される要素の範囲は、 への最初の呼び出しの前に設定されますcallbackfn。filter の呼び出しが開始された後に配列に追加された要素は、 によってアクセスされませんcallbackfn。配列の既存の要素が変更された場合、渡され callbackfnた値はフィルターがアクセスしたときの値になります。filter の呼び出しが開始された後、アクセスされる前に削除された要素はアクセスされません。

それ自体がすでに大変な作業です。ECMAScript エンジンが実行する必要がある多くのステップ。

続けて、次のように言います。

filter メソッドが 1 つまたは 2 つの引数で呼び出されると、次の手順が実行されます。

引数として値を渡してO呼び出した結果をみましょう。引数を指定して の内部メソッドを呼び出した結果をとします。させてToObjectthislenValue[[Get]]OlengthlenToUint32(lenValue). IsCallable(callbackfn) が false の場合、TypeError 例外をスローします。thisArg が指定された場合、T を thisArg とします。それ以外の場合は、T を未定義にします。A を式 new Array() によって作成されたかのように作成された新しい配列とします。ここで、Array はその名前の標準の組み込みコンストラクターです。k を 0 とします。0 とします。繰り返しますが、k < len Pk を ToString(k) とします。kPresent を、O の [[HasProperty]] 内部メソッドを引数 Pk で呼び出した結果とする。kPresent が true の場合、kValue を引数 Pk で O の [[Get]] 内部メソッドを呼び出した結果とします。T を this 値として、kValue、k、および O を含む引数リストを使用して、callbackfn の [[Call]] 内部メソッドを呼び出した結果を selected とします。ToBoolean(selected) が true の場合、[[DefineOwnProperty]] を呼び出します。引数 ToString(to) を持つ A の内部メソッド、プロパティ記述子 {[[Value]]: kValue、[[Writable]]: true、[[Enumerable]]: true、[[Configurable]]: true}、および false。を 1 ずつ増やします。k を 1 増やします。A を返します。

filter メソッドの長さプロパティは 1 です。

注意 フィルタ関数は意図的に汎用的です。この値が Array オブジェクトである必要はありません。したがって、メソッドとして使用するために他の種類のオブジェクトに転送できます。フィルター関数をホスト オブジェクトに正常に適用できるかどうかは、実装に依存します。

このアルゴリズムに関する注意事項:

  • 述語関数がデータを変更するのを防ぎます
  • オプションで、述語関数の実行コンテキストを設定します
  • 配列内の削除された値とギャップを無視します

多くの場合、これらは必要ありません。そのため、独自のメソッドを作成する場合filter、ほとんどの場合、これらの手順を実行することさえ気にしません。

すべての ES5.1 準拠の JavaScript エンジンは、そのアルゴリズムに準拠する必要があるため、 を使用するたびにこれらすべての手順を実行する必要がありますArray#filter

これらのステップの一部のみを実行するカスタム作成メソッドの方が高速であることは驚くべきことではありません :)

独自のfilter関数を作成する場合、上記のアルゴリズムほど複雑ではない可能性があります。ユースケースによっては、配列をフィルタリングするためだけに必要ない場合があるため、配列をオブジェクトにまったく変換しない可能性があります。

于 2013-02-01T14:13:16.140 に答える
3

面白いことがわかりました。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)

この場合、ネイティブの「フィルター」の方がはるかに高速です。したがって、配列インデックスではなくプロパティを反復すると思います。

ここで「大きな」問題に戻りましょう ;)。

于 2013-02-14T18:28:21.017 に答える
2

あなたのスクリプトは間違っていませんか?

あなたはarray.filter1000回測定を行っており、合計を1000で割って提示しています

測定はJQuery.grep1 回で、合計を 1000 で割って提示します。

これは、比較に使用する値よりも実際には grep が 1000 倍遅いことを意味します。

firefox での簡単なテストでは、次の結果が得られます。

Machine 1:
average filter - 3.864
average grep - 4.472

Machine2:
average filter - 1.086
average grep - 1.314

クロムでのクイックテストは次のとおりです。

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% 遅くなります。

于 2016-11-21T07:45:04.510 に答える
-1

TLDR; grep の方がはるかに高速です... (理由についてのヒントはここにあります)

.filter はこれをオブジェクトに強制し、コールバック IsCallable をチェックしてこれを設定し、各反復でプロパティの存在をチェックするのに対し、.grep はこれらのステップを想定してスキップするように見えます。

テストに使用したスクリプトは次のとおりです。

function test(){
var array = [];
for(var i = 0; i<1000000; i++)
{
array.push(i);
}

var filterResult = []
for (var i = 0; i < 1000; i++){
var stime = new Date();
var filter = array.filter(o => o == 99999);
filterResult.push(new Date() - stime);
}

var grepResult = [];
var stime = new Date();
var grep = $.grep(array,function(i,o){
return o == 99999;
});
grepResult.push(new Date() - stime);

$('p').text('average filter - '+(filterResult.reduce((pv,cv)=>{ return pv +cv},0)/1000))
$('div').text('average grep - '+(grepResult.reduce((pv,cv)=>{ return pv + cv},0)/1000))
}
test();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<p></p>
<div></div>

于 2016-10-06T14:24:13.803 に答える