39

在一个问题中讨论了 jQuery 和原生 JS 将如何相互对抗。

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

令人惊讶的是,在将其添加到测试后,我学到了一个教训:Testsuite

Edgecases当然有不同的结果。

任何人都知道为什么$.grep应该比本机方法快 3 倍以上Arrray.filter

编辑:我修改了测试以使用来自 MDN 的过滤器垫片,结果非常有趣:

  • Chrome:即使是 MDN shim 也比原生方法快,jQuery 遥遥领先
  • Firefox:shim 比本机方法慢一点,jQuery 领先

最后是我希望看到的结果

  • Internet Explorer:native 方法是最快的,然后是 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)如下:

callbackfn应该是一个函数,它接受三个参数并返回一个可强制转换为布尔值的值truefalse. 对数组中的每个元素按升序filter调用一次,并构造一个包含所有返回值的新数组。仅对实际存在的数组元素调用;它不需要数组的缺失元素。callbackfncallbackfntruecallbackfn

如果thisArg提供了参数,它将用作this 每次调用的值callbackfn。如果未提供, undefined则改为使用。

callbackfn使用三个参数调用:元素的值、元素的索引和被遍历的对象。

filter不会直接改变调用它的对象,但对象可能会通过调用callbackfn.

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

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

然后它继续说以下内容:

当使用一个或两个参数调用过滤器方法时,将执行以下步骤:

O是调用ToObjectthis值作为参数传递的结果。让是使用参数lenValue调用[[Get]]内部方法的结果。让_OlengthlenToUint32(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 为真,则令 kValue 为使用参数 Pk 调用 O 的 [[Get]] 内部方法的结果。令 selected 为调用 callbackfn 的 [[Call]] 内部方法的结果,其中 T 作为 this 值,参数列表包含 kValue、k 和 O。如果 ToBoolean(selected) 为 true,则调用 [[DefineOwnProperty]] A 的内部方法,带参数 ToString(to),属性描述符 {[[Value]]: kValue, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true} 和 false。增加 1。将 k 增加 1。返回 A。

filter方法的length属性为1。

注意过滤器功能是故意通用的;它不要求它的 this 值是一个 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.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%。

于 2016-11-21T07:45:04.510 回答
-1

TLDR;Grep 的速度要快一个数量级......(提示为什么可以在这里找到

在我看来,.filter 强制它是 Object,检查回调 IsCallable 并在其中设置 this 以及检查每次迭代中是否存在属性,而 .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 回答