我读到建议用not reading the length attribute of an array every iteration in the loop header来优化JavaScript中的循环。
所以,我们应该这样做:
var names = ['George','Ringo','Paul','John'];
for(var i=0,j=names.length;i<j;i++){// Read array length once and assign it to a variable
doSomeThingWith(names[i]);
}
而不是:
var names = ['George','Ringo','Paul','John'];
for(var i=0;i<names.length;i++){
doSomeThingWith(names[i]);
}
然而,我created a small testcase来比较这两种技术,但有时第一种情况更快,有时第二种情况更快。
您会推荐哪个版本?
答案 0 :(得分:21)
首先,我应该说这个答案是在2011年写的,随着时间的推移这些事情会发生变化(因为浏览器解释器会优化越来越多的东西)所以如果你真的想知道世界的当前状态,你必须运行测试在当前的浏览器上。
在任何版本的IE上运行own jsperf test。在那里,您将看到两种方法或许多其他旧浏览器之间的一致差异。你显然只在Chrome上运行它,它非常快速且经过优化,两种方法之间的差异可以忽略不计。在IE9上(这可能比IE7和IE8更好),预先缓存长度的方法快31%。
A jsperf test designed for this question给出了这个问题的定量结果。在这样的问题中,我们应该去jsperf看看真正的区别是什么,而不是那么多的猜测。
它显示了我尝试的浏览器的差异,从几乎没有差异到相当大的差异,具体取决于浏览器。 在Chrome中,几乎没有区别。在IE9中,首先存储长度几乎快50%。
现在,这种速度差异对您的脚本是否重要取决于具体的代码。如果你有一个庞大的数组,你经常循环,它可以在一些浏览器中使用这种形式有一个有意义的区别:
for (var i = 0, len = list.length; i < len; i++) {
// do code here
}
在使用某些DOM函数返回的实时伪数组的slightly different test case中,速度仍有差异,但不是放大(我预计DOM伪实时数组的差异会更大,但它不是“T)。
在实践中,我倾向于使用短版本(更少打字),因为我认为我的代码部分不是速度关键和/或数组不大而我会使用预先缓存的较长版本如果我有意识地考虑速度或者数组是巨大的,或者我在同一个数组上做了很多迭代,那么这个长度。
预缓存长度还有其他一些编程原因。如果您将在循环期间向元素的末尾添加元素,并且您不希望循环迭代这些新添加的元素,那么您将需要预加载长度并且仅迭代最初存在的元素
for (var i = 0, len = list.length; i < len; i++) {
if (list[i] == "whatever") {
list.push("something");
}
}
请记住,浏览器不断发展并添加越来越多的优化,因此2011年显示出巨大优势的优化可能会在未来基本构建到更现代的浏览器中,因此不再需要手动编码优化。所以,如果你想为今天的表现优化某些东西,你必须在今天的浏览器中进行测试,你不能只依赖你读过的可能有几年的东西。
答案 1 :(得分:13)
这个建议总是充其量只是微优化,并且所有的工作都是在Javascript引擎的速度上完成的,它不太可能是一个可衡量的差异。也许在一个非常长的非常紧凑的循环中它可能会有所作为,但我对此表示怀疑。
出于某种原因,程序员倾向于专注于速度高于一切,即使它是无根据的。考虑正确性,然后考虑可读性。
答案 2 :(得分:5)
我会推荐第二个:
var names = ['George','Ringo','Paul','John'];
for (var i = 0; i < names.length; i++) {
doSomeThingWith(names[i]);
}
因为它更简洁,更具惯用性。除非你做了一些荒谬的微优化,否则你不需要使用第一个。
答案 3 :(得分:5)
作为一般规则,缓存循环的“停止值”(在您的情况下为names.length)只有在计算值时才有价值。对于有问题的数组,它只是一个查找,所以你通过缓存它将获得很少。
答案 4 :(得分:4)
定义"really necessary"
。
如果你循环遍历4个元素的数组,我不认为即使是IE也会介意,但请记住,你可能需要遍历一些dom元素;假设您有一个列表(ul
),其中包含1.000.000(或更多)入口(li
)。我认为声明一个额外的变量可以节省你检查ul的长度属性一百万次
也许我夸大了百万部分,但只看10000 li
的{{3}}。
优化的循环比“普通”循环快几百倍。
我的结论:优化你的循环...它不会伤害你(或你的代码或浏览器)。
答案 5 :(得分:3)
我建议
var names = ['George','Ringo','Paul','John'];
var length = names.length;
for(var i=0;i<length;i++){
doSomeThingWith(names[i]);
}
答案 6 :(得分:1)
2017更新回答
您应该使用优化/最佳实践方式。
在你的确切例子中:它是如此微不足道,无关紧要。即使是@ jfriend00所说的性能差异为50%,也没有多大意义。 CPU(包括当前的智能手机)每秒可以进行数百万次计算。意味着毫秒的分数甚至不会注册给用户,这与@Ned Batchelder发布的内容一致。
然而,编码不应该是你可以逃脱的。也就是说,正如@DwB所说的那样,“......停止价值......只有在计算出的价值时才有价值。”考虑到这一点,下面的代码给出了一个时间浪费函数返回停止值的例子。这里很明显速度有多么不同。将服务器,复杂客户端代码和其他密集型计算的潜在缺点相乘,您将通过使用最佳实践来改善用户体验。
var eCount = document.getElementById("loopCount");
var waitDiv = document.getElementById("waitDiv");
var runButton = document.getElementById("runButton");
var interCount = eCount.value.replace(/\D/g,'');
var names = ['George','Ringo','Paul','John'];
eCount.addEventListener("input", function(){
var value = parseInt(this.value.replace(/\D/g,'')).toLocaleString();
this.value = value.toLocaleString();
});
function runLoop(){
interCount = eCount.value.replace(/\D/g,'');
waitImg(true);
setTimeout(function(){
var cachedTime = loopTest("cached");
var inlineTime = loopTest("inline");
document.getElementById( "cached" ).innerText = cachedTime+" Cached";
document.getElementById( "inline" ).innerText = inlineTime+" Not Cached";
waitImg(false);
}, 100); // delay to allow update of DOM with waitimg gif
}
function loopTest(meth){
var worthlessVariable = 0;
var t0 = performance.now();
if( meth == "cached" ){
for( var i = 0, len = busyCalulations(); i < len; i++) {
worthlessVariable = i;
}
}else{
for( var i = 0; i < busyCalulations(); i++) {
worthlessVariable = i;
}
}
var t1 = performance.now();
return (t1 - t0);
}
function busyCalulations(){
// garbage math to simulate doing something
// it returns interCount after some pointless math
var limit = Math.floor(Math.random() * 20) + 20;
return interCount*(limit*names.length)/(limit*names.length);
}
function waitImg(txt){ // display wait timer
if (txt === true){
waitDiv.style.visibility = "visible";
runButton.style.visibility = "hidden";
}else{
waitDiv.style.visibility = "hidden";
runButton.style.visibility = "visible";
}
}
<h1>Loop Tester</h1>
<form onSubmit="return false;">
Loop Length <input id="loopCount" type="text" value="100,000"><br>
<br><br>
<button id="runButton" onClick="runLoop();">Run Test</button>
<div id="waitDiv" style="visibility: hidden"><img src="https://i.stack.imgur.com/5qXc3.gif"></div>
<br><br>
</form>
<div><p>Times are in milliseconds</p>
<div id="cached"></div>
<div id="inline"></div>
</div>
答案 7 :(得分:0)
请参阅this帖子,该文章讨论优化JS循环。
所以对你来说一个简单的解决方案就是:
let arr = ["a", "b", "c", "d", "e"]
let i = arr.length
while(i--) {
callFn();
}
与其他传统循环技术(如
)相比,上述代码运行速度更快for(let i = 0; i < arr.length; i++) {}
因为上面的代码必须在每次迭代时获取arr.length
。