jQuery.grep与Array.filter的性能

时间:2013-02-01 13:34:37

标签: javascript jquery performance

question中讨论了jQuery和本机JS如何相互作用。

当然,vanilla解决方案的执行速度要快得多,因为它不会处理整个数组我提议使用Array.filter,我相信它至少比$.grep更快。

令人惊讶的是,在将其添加到测试中后,我接受了一课: Testsuite

Edgecases当然会有不同的结果。

任何人都知道为什么$.grep应该比原始方法Arrray.filter快3倍?

编辑:我修改了测试以使用filter shim from MDN,结果非常有趣:

  • Chrome:即使是MDN shim也比本机方法更快,jQuery方向领先
  • Firefox:shim比本机方法慢一点,jQuery方向前进

最后一个结果就像我希望它能在

中看到
  • Internet Explorer: 本机方法是最快的,然后jQuery,shim是最慢的(也许这只是IEs相当弱的JS引擎的结果......)

5 个答案:

答案 0 :(得分:14)

this blog post 上找到(也会进行相同类型的测试):

  

如果你阅读了filter的文档,你就会明白为什么它会慢得多。

     
      
  1. 它会忽略数组中已删除的值和间隙
  2.   
  3. 它可选地设置谓词函数的执行上下文
  4.   
  5. 它阻止谓词函数改变数据
  6.   

答案 1 :(得分:8)

Section 15.4.4.20 of the ECMAScript 5.1 spec定义Array.prototype.filter(callbackfn, thisArg)如下:

  

callbackfn应该是一个接受三个参数的函数   返回一个可强制为布尔值true或的值   falsefilter为每个元素调用callbackfn一次   数组,按升序排列,并构造一个新的数组   callbackfn返回true的值。调用callbackfn   仅适用于实际存在的数组元素;它不被称为   缺少数组的元素。

     

如果提供了thisArg参数,则会将其用作this   每次调用callbackfn的值。如果没有提供,   而是使用undefined

     使用三个参数调用

callbackfn:元素的值,   元素的索引和被遍历的对象。

     

filter不会直接改变调用它的对象,但是   通过调用callbackfn可以突变对象。

     

过滤器处理的元素范围在第一次调用之前设置   到callbackfn。在数组之后附加到数组的元素   callbackfn不会访问过滤器开始调用。如果存在   数组的元素更改为传递给它的值   callbackfn将是过滤器访问时的值;   在调用过滤器之后和之前删除的元素   被访问的人不会被访问。

这本身已经做了很多工作; ECMAScript引擎需要执行的许多步骤。

然后继续说以下内容:

  

当使用一个或两个参数调用filter方法时,   采取以下步骤:

     

O成为调用ToObject传递this值的结果。{   论点。   让lenValue成为调用[[Get]]内部的结果   参数O的{​​{1}}方法。   让lengthlen。   如果IsCallable(callbackfn)为false,则抛出TypeError异常。如果   thisArg被提供,让T成为thisArg;否则让T不确定。让A   是一个新的数组,就好像通过表达式new Array()where Array一样   是具有该名称的标准内置构造函数。设k为0.让   重复,而k <0。 len让Pk成为ToString(k)。让kPresent成为   调用O的[[HasProperty]]内部方法的结果   争论Pk。如果kPresent为真,那么让kValue成为结果   用参数Pk调用O的[[Get]]内部方法。让   选择是调用[[Call]]内部方法的结果   callbackfn用T作为此值和参数列表包含   kValue,k和O.如果ToBoolean(selected)为true,则调用   [[DefineOwnProperty]]带参数的A的内部方法   ToString(to),属性描述符{[[Value]]:kValue,[[Writable]]:   true,[[Enumerable]]:true,[[Configurable]]:true},false。   增加1.将k增加1.返回A.

     

长度属性   过滤方法是1。

     

注意过滤功能是有意通用的;它不需要   它的这个值是一个Array对象。因此它可以   转移到其他种类的对象用作方法。是否   filter函数可以成功应用于宿主对象   实现相关的。

有关此算法的一些注意事项:

  • 它阻止谓词函数改变数据
  • 它可选地设置谓词函数的执行上下文
  • 它会忽略数组中已删除的值和间隙

在很多情况下,这些都不需要。因此,在编写自己的ToUint32(lenValue)方法时,大多数情况下您甚至都不愿意执行这些步骤。

每个符合ES5.1标准的JavaScript引擎都必须符合该算法,因此每次使用filter时都必须执行所有这些步骤。

任何只执行这些步骤的自定义编写方法都会更快,这应该不足为奇了。)

如果您编写自己的Array#filter函数,则可能不会像上述算法那样复杂。也许您根本不会将数组转换为对象,因为根据用例,可能不需要仅仅过滤数组。

答案 2 :(得分: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)

在这种情况下,本机“过滤器”要快得多。所以我认为它迭代属性而不是数组索引。

现在让我们回到'大'问题;)。

答案 3 :(得分:2)

你的剧本不对吗?

对于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

镀铬快速测试给出:

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%。

答案 4 :(得分:-1)

TLDR; Grep速度更快......(暗示为什么can be found here

  

在我看来,像.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>