如何优化这个jQuery循环的执行时间?

4
我有一个循环,它遍历了所有带'class GridCell'的表格单元格中的div。在某些情况下,这是必需的,例如网格或列被调整大小时。
我增加了行和列的数量,以便更容易看到时间差异,目前循环大约需要750毫秒。
但是我不理解的是,'循环的一部分'要快得多。请参见以下三个循环。第一个循环很慢。只执行第一个循环的一部分的第二个和第三个循环非常快速。
//Around 750 ms
$('table.Grid tbody td.GridCell > div').each(function() {
    var width = $(this).parent().width();
    $(this).css('width', width - 3);
});

//Around 60 ms
$('table.Grid tbody td.GridCell > div').each(function() {
    var width = $(this).parent().width();
});

//Around 15 ms
$('table.Grid tbody td.GridCell > div').each(function() {
    $(this).css('width', 100);
});

一条指令只需要60或15毫秒,但两个指令一起需要750毫秒。是什么造成了这种差异呢?

附注:执行循环的顺序无关紧要。第一个循环总是比其他循环慢得多,即使它是在最后执行的。


3
既然在第一个示例中使用了两次 $(this),那么如果你创建一个对它的单一引用,会怎样呢? - Fabrizio Calderan
你正在每次迭代中调用此方法,var width = $(this).parent().width(); 由于它不会改变,在循环之前在外部调用它。 - Anthony Palmer
@FabrizioCalderan 在其他两个循环中,他也两次调用了$(this),如果将它们合并起来,那么缓存一个jQuery对象很可能不是瓶颈所在。 - David Hellsing
@FabrizioCalderan,我已经尝试过了,但执行时间没有任何明显的变化。我猜这个改进太微小了,以至于看不出来。 - Erik Dekker
每一行的每个单元格都有 .GridCell 类吗? - Alnitak
显示剩余4条评论
9个回答

2
在第一个循环中,你计算了一个计算宽度,然后在每次迭代中将其应用于另一个元素。在第二个循环中,你只是计算宽度,并将其分配给一个变量。而在第三个循环中,你只是应用静态内联样式。
我的意思是,后两个循环结合起来并不能等同于第一个的功能,因此第一个比其他两个组合更慢并不奇怪。
你应该尝试像这样做:
var $divs = $('table.Grid tbody td.GridCell > div'),
    m = [];

// case 1
$divs.each(function() {
    var width = $(this).parent().width();
    $(this).css('width', width - 3);
});

// case 2
$divs.each(function() {
    m.push( $(this).parent().width() );
}).each(function(i) {
    $(this).css('width', m[i] - 3);
});

我在这里进行了一个简单的性能测试:http://jsperf.com/tablewidth,差异似乎非常小。

谢谢你!你真的知道你在说什么。Case 1 是我的慢循环(700+),Case 2 大约是70-80毫秒。你现在应该休息一天,喝些免费啤酒。 - Erik Dekker
谢谢,只是请注意我没有尽力优化计算(我需要一些HTML来做到这一点),我只是试图回答你关于执行时间巨大差异的问题。 - David Hellsing
这有点难在这里展示,因为该代码是从第三方网格生成的,缺少一些功能。我添加了一些扩展,现在它完全可调整大小,而你修复了其中的缓慢部分。所以我很高兴。 - Erik Dekker

2
// collect the widths from the first row only
var widths = $('table.Grid tbody tr:first-child td.GridCell').map(function(idx) {
  return $(this).width() - 3;

  // or use:
  // return this.clientWidth - 3;
  // if you're not targeting IE <= 7
});

// apply the widths to each div
$('table.Grid tbody td.GridCell > div').each(function(idx) {
  this.style.width = widths[idx % widths.length] + 'px';
});

使用.index()是很慢的,因为它必须遍历元素的兄弟节点并计算它们的数量。这就是为什么我检查每个td是否具有.GridCell类,然后在我的答案中使用了.css('width', function(...) {}).css函数的函数版本会自动将元素索引作为参数提供。 - Alnitak
谢谢,它的工作效果很好,比我的循环快得多,使用这种解决方案我可以得到大约270毫秒的时间。 - Erik Dekker
这确实好多了!David的解决方案时间是76,76,75,75。你的解决方案时间现在是76,79,78,76,所以一样快。我想这就是我们能得到的最好结果 :-) - Erik Dekker
而对于统计数据:使用已注释的代码,我们可以达到14、15、15、14毫秒的速度 :-) 目前我们确实有一些IE7的客户,但我会记住这一点的。谢谢!(你确定这在IE7中不起作用吗?) - Erik Dekker
@ErikDekker clientWidth 在 IE7 及其之前的版本中计算错误。但仅当用户对页面应用缩放(例如按下 Ctrl 和 +)时才会出现此问题。来源。如果这对您不是问题,那么您可以使用它。 - Yoshi
显示剩余5条评论

1
var $rows = $('table.Grid tbody').children('tr');

// we only need the widths from the first row
var widths = $rows.first().children('td').map(function() {
    return $(this).width() - 3;
}).get();

// process each row individually
$rows.each(function() {
    $('td.gridCell > div', this).css('width', function(i) {
         return widths[i];
    });
});

如果您使用了内部表格,则 $('td > div', this) 可能需要更改为 $('> td > div', this) - Kees C. Bakker
你如何确定所有TR > TD的宽度相同?是通过colspan吗? - David Hellsing
@David 我承认,我只是猜测没有colspans。如果没有原始问题的HTML,我们无法确定。 - Alnitak
据我所知,$('> ..,', context')已经被弃用。 - Alnitak
它比我的循环快得多,但是我会得到+/- 200毫秒。所以David的解决方案仍然更快。除此之外,它还没有真正起作用,div太大了,但我想可以通过其他选择器或其他方法来解决。 - Erik Dekker
@ErikDekker 没有你的HTML,很难知道额外循环所创建的开销有多大。 - Alnitak

0

在你的第一个例子中

$('table.Grid tbody td.GridCell > div').each(function() {
    var width = $(this).parent().width();
    $(this).css('width', width - 3);
});

你正在两次评估$(this)。尝试改为写成

$('table.Grid tbody td.GridCell > div').each(function() {
    var $this = $(this);
    var width = $this.parent().width();
    $this.css('width', width - 3);
});

或者更好的是(编辑:不确定),尝试使用传递给each()方法的当前元素。

$('table.Grid tbody td.GridCell > div').each(function(i, el) {
    var $this = $(el);
    var width = $this.parent().width();
    $this.css('width', width - 3);
});

你认为为什么 $(el)$(this) 更好? - Alnitak
我猜测你想用 this 来评估当前范围。不确定它是否是真正有效的优化(而且我现在不能在 jsperf 上进行测试)。 - Fabrizio Calderan
据我所知,访问this并不比访问已声明的参数慢。如果有什么区别的话,我会期望相反,因为每个函数都必须有一个this上下文。 - Alnitak
缓存一个jQuery对象或不缓存并不是一个大的瓶颈,进行一个快速测试:http://jsperf.com/thisjq - David Hellsing
@David 你在开玩笑吗?那可是整整快了6%!! - Alnitak

0

如果你使用了什么呢?

$('table.Grid tbody td.GridCell > div').each(function(i,domEle) {
    $(domEle).css('width', $(domEle).parent().width() - 3);
});

同时尝试在jQuery中使用优化选择器


抱歉,但这并没有在性能时间上给我任何(可见的)结果。 - Erik Dekker

0

假设父元素的宽度计算不是动态的(子元素的调整不会影响父元素),并且宽度是恒定的(前提条件很大)。

var width = $(this).parent().width();

$('table.Grid tbody td.GridCell > div').each(function() {
    $(this).css('width', width - 3);
});

循环的结构方式使得每次迭代父元素的宽度可能不同。只有当将其更改为先遍历所有列,然后再遍历行(作为内部循环)时,才能重用父元素的宽度。 - Yoshi
是的,提交后我意识到这取决于您的表格布局。 - Anthony Palmer
如果我没错的话,如果宽度不是动态的,用户可以使用 $('table.Grid tbody td.GridCell> div').css('width',width-3); - GajendraSinghParihar
父元素是表格单元格。因此,每列的宽度都会改变,所以无法这样做。 - Erik Dekker

0

这不一定是JS的问题!忘掉循环,你根本不需要使用JS/jQuery来实现所需的功能。

为了使.GridCell的子div占据所有可用宽度减去3px,您可以使用以下CSS规则:

.GridCell>div {
  width:auto;             /*take up all available width (for block elements)*/
  margin:0 1.5px;         /*add a 1.5px buffer on both sides of the div*/
}

如果你想要非常高级,而且不在意浏览器兼容性的话,你可以使用CSS3的calc()函数:

.GridCell>div {
  width:calc(100% - 3px); /*look up vendor-specific prefixes of the property
                            if you want for this to work on more browsers*/
  margin: 0 auto;         /*now that the width is calculated correctly above,
                            center the div. Or don't!*/
}

Et voila


谢谢。我尝试了类似这样的东西,但宽度和溢出不再起作用了。使用自动或百分比宽度时,我无法将列缩小到表格单元格中的内容大小。这在表格单元格中使用固定宽度可以做到。 - Erik Dekker
@ErikDekker:你说的“但是宽度和溢出不再起作用了”是什么意思?你在上面的fiddle中看到CSS完美地工作;如果有其他样式干扰,那就是另外一个问题。修复CSS,不要为这种琐碎的样式使用脚本。不幸的是,我的猜测能力不足以解决这个问题,也许如果你添加了一个带有实际内容的jsfiddle呢? - Oleg
好的,这是在一个网格中,大约有5到x个列。每个列都可以单独调整大小。但是你的解决方案无法实现此功能。如果使用CSS样式对某一列进行了设置,则无法将该列拖动至小于其中文本的大小。 - Erik Dekker
@ErikDekker:如果你无法提供一个jsfiddle或原始链接,那么实际上没有什么可以做的。真可惜 - 这可能只是一个快速修复,而且最好使用CSS来解决CSS问题 ಠ_ಠ 最重要的是 - 将这个逻辑转移到渲染引擎上,这将带来最佳的性能提升。 - Oleg
你说得有道理。但我的问题是,我正在使用一个第三方网格,周围有各种自定义包装器和代码。这个小循环是该网格的一些自定义扩展,使其完全可调整大小。在jsfiddle中放置类似这样的东西有点困难。这就是为什么我只问了一个关于该循环性能的问题。 - Erik Dekker

0

好的,这是另一种选择:

$('table.Grid tbody td.GridCell > div').css('width', function() {
    return $(this.parentNode).width() - 3;
});

不行,这和我的循环一样慢(> 700毫秒),但是尝试得不错;-) - Erik Dekker
@ErikDekker 我猜想是因为对每个元素进行宽度的评估和更改而导致重复执行,这可能导致重新布局。 - Alnitak

0

对于我来说很难说出确切的细节,但我理解这个问题是这样的:

var width = $(this).parent().width(); - 你调用它,浏览器被迫获取所选元素的实际尺寸。 $(this).css('width', 100); - 这会导致浏览器重新排版文档。因此,你运行这一行,浏览器必须重新计算页面的一部分。

为什么第一个循环比其他两个循环慢?我认为当你只做 var width = $(this).parent().width(); 时,它可以一次计算所有宽度并从缓存中给出它们。 现在当你只做这一行 $(this).css('width', 100); - 浏览器可以等待脚本执行。然后一次性重绘所有更新的元素。 但是当你做第一个循环时,你强制浏览器计算宽度,然后更新某些元素。尺寸缓存被破坏了。现在你再次想要获取宽度,浏览器必须重新绘制页面。因此,每个周期它都会反向重绘页面,而不是其他两个循环,它只需要一次就可以完成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接