在question中讨论了jQuery和本机JS如何相互作用。
当然,vanilla解决方案的执行速度要快得多,因为它不会处理整个数组我提议使用Array.filter
,我相信它至少比$.grep
更快。
令人惊讶的是,在将其添加到测试中后,我接受了一课: Testsuite
Edgecases当然会有不同的结果。
任何人都知道为什么$.grep
应该比原始方法Arrray.filter
快3倍?
编辑:我修改了测试以使用filter shim from MDN,结果非常有趣:
最后一个结果就像我希望它能在
中看到答案 0 :(得分:14)
在 this blog post 上找到(也会进行相同类型的测试):
如果你阅读了
filter
的文档,你就会明白为什么它会慢得多。
- 它会忽略数组中已删除的值和间隙
- 它可选地设置谓词函数的执行上下文
- 它阻止谓词函数改变数据
醇>
答案 1 :(得分:8)
Section 15.4.4.20 of the ECMAScript 5.1 spec定义Array.prototype.filter(callbackfn, thisArg)
如下:
callbackfn
应该是一个接受三个参数的函数 返回一个可强制为布尔值true
或的值false
。filter
为每个元素调用callbackfn
一次 数组,按升序排列,并构造一个新的数组callbackfn
返回true
的值。调用callbackfn
仅适用于实际存在的数组元素;它不被称为 缺少数组的元素。如果提供了
使用三个参数调用thisArg
参数,则会将其用作this
每次调用callbackfn
的值。如果没有提供, 而是使用undefined
。
callbackfn
:元素的值, 元素的索引和被遍历的对象。
filter
不会直接改变调用它的对象,但是 通过调用callbackfn
可以突变对象。过滤器处理的元素范围在第一次调用之前设置 到
callbackfn
。在数组之后附加到数组的元素callbackfn
不会访问过滤器开始调用。如果存在 数组的元素更改为传递给它的值callbackfn
将是过滤器访问时的值; 在调用过滤器之后和之前删除的元素 被访问的人不会被访问。
这本身已经做了很多工作; ECMAScript引擎需要执行的许多步骤。
然后继续说以下内容:
当使用一个或两个参数调用filter方法时, 采取以下步骤:
让
O
成为调用ToObject
传递this
值的结果。{ 论点。 让lenValue
成为调用[[Get]]
内部的结果 参数O
的{{1}}方法。 让length
为len
。 如果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>