4

I have a loop thats going through all div's in a table cel with class 'GridCell'. It is required that this happens in some situations, when the grid or columns are resized.

I extended the number of rows and columns, to see more time differences, and the loop is around 750 ms right now.

But what I don't understand, is that 'just a part of the loop' is a lot faster. See the following three loops. The first is slow. The second and third loop that only do a part of the first loop are really fast.

//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);
});

So one line, is only 60 or 15 ms, but the two together are 750. What makes this difference?

p.s. It doesn't matter in what sequence I execute the loops. The first loop is always a lot slower then the others, also when that loop is executed last.

9
  • 3
    Since you're using it twice, what if you create a single reference to $(this) in your first example? Commented Aug 29, 2012 at 8:48
  • You are calling this method for every iteration, var width = $(this).parent().width(); since its not going to change, call it outside of the loop before the each. Commented Aug 29, 2012 at 8:53
  • @FabrizioCalderan He is calling $(this) twice in the other two loops as well if you combine them, so caching a jQuery object is not likely the bottleneck here. Commented Aug 29, 2012 at 8:54
  • @FabrizioCalderan, I tried that already, but doesn't give any visible change in execution times. I guess its to marginal to see it back in there. Commented Aug 29, 2012 at 8:58
  • Why not collect the widths in an array in an initial first loop, and in the second loop set them to each array value -3?<BR/> You would really have to comment your code to explain why you were doing it this way :-) Commented Aug 29, 2012 at 9:05

9 Answers 9

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';
});
Sign up to request clarification or add additional context in comments.

10 Comments

using .index() is slow because it has to traverse the element's siblings and count how many there are. This is why I checked whether every td has the .GridCell class and then used .css('width', function(...) {} in my answer. The function version of .css automatically supplies the elements index as a parameter.
I don't think that'll work for more than 1 row - the idx parameter is the index within the set, not the index relative to the element's parent.
I still think that's a fluke - if you console.log() the idx parameter you should see it reach 99, and not repeatedly go from 0..9
oh, I see what you've done... hmm. That's an abuse of the outer .width call since you're not returning a new value.
Thanks, it does work good, and is a lot faster then my own loop, I get times around 270 ms with this solution.
|
2

In the first loop, you are calculating a computed width, then applying that to another element for each iteration. In the second loop, all you do is computing the width, and assigning it to a variable. And in the third loop, you are simply applying static inline styles.

What I’m saying is that the last two loops combined does not equal the functionality of the first, so it’s no surprise that the first one slower than the other two combined.

You should try something like:

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);
});

I made a simple perf test here: http://jsperf.com/tablewidth and the difference seems to be very small.

3 Comments

Thanks for this! You really know what you are talking about. Case 1 is my own slow loop (700+), case 2 is about 70-80 ms. You deserve a day off to drink some free beer now.
Thanks, just note that I did not try my best to optimize the computations (I would need some HTML for that), I was just trying to answer your question about the big differences in execution times.
Its kinda hard to show that here, because the code is generated from a third party grid, that was lacking some functionality. I added some extensions so its fully resizable now, and you fixed the slow part of that. So i'm happy.
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];
    });
});

6 Comments

$('td > div', this) might need to be $('> td > div', this) if you have inner tables.
How do you know all TR > TDs have same width? colspan?
@David I admit, I was just guessing that there are no colspans. Without the OP's HTML we can't know for sure.
@KeesC.Bakker AFAIK $('> ..,', context') is deprecated.
It is a lot faster then my own loop, but with this I get +/- 200 ms. So David's solution is still faster. Next to that, its not really working yet, the div's get way too big, but I guess that can be fixed with some other selectors or something
|
0

Assuming the calculation of parent width isn't dynamic (the resizing of the child dosent affect the parent) and width is constant (big if's).

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

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

4 Comments

The way the loop is structured the parent width is likely to be different on each iteration. Only if it was changed to first move through all columns and then trough the rows (as an inner loop) could you reuse the parent width.
Yes, I realised that right after submitting, it depends on your table layouts.
if I am not wrong if the width is not dynamic the use may use $('table.Grid tbody td.GridCell > div').css('width', width - 3);
The parent is a table cell. So the width changes for every column, so can't do this
0

in your first example

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

you're evaluating $(this) twice. try to write instead

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

or even better (edit: not sure) try also to use the current element passed along the each() method

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

5 Comments

Why do you think that $(el) is better than $(this) ?
I suppose that using this you're trying to evaluate the current scope. Not sure if it is a real and effective optimization (and I cannot do some test on jsperf right now)
AFAIK this is no slower than accessing a declared parameter. If anything I'd expect the opposite since every function must have a this context.
Caching a jQuery object or not is not a big bottleneck, a quick test: jsperf.com/thisjq
@David are you joking? That's six whole percent faster!!
0

Hard for me to say exact details, but I understand this problem like this:

var width = $(this).parent().width(); - you call this and browser is forced to get you real dimentions of selected element. $(this).css('width', 100); - this cause browser to reflow the document. So you run this line and browser must recalculate a part of page.

Why first works slower than two other loops? I see it like when you do only var width = $(this).parent().width(); it can calculate all width once and give them from cache. Now when you do only this line $(this).css('width', 100); - browser can wait untill script is executed. And redraw all update elements at once. But when you do the first loop you force browser calculate width, than you update some element. Dimentions cache is broken. Now you again want to get a width and browser must redraw page. So each cycle it redraws a page in opposit to other two loops when it can do that only once.

Comments

0

This does not have to be a JS issue! Forget the loops, you don't need to use JS/jQuery to achieve the desired functionality at all.

To make the child div of .GridCell take up all available width minus 3px, you can use the following CSS rule:

.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*/
}

If you want to be really fancy and don't care about somewhat poor browser compatibility matrix, you could rely on 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

5 Comments

Thanks. I tried something like this, but the width and overflows are not really working anymore then. With auto or percentual width, I can't resize a column smaller then the content in the table cells. That does work with fixed widths in the table cells.
@ErikDekker: what do you mean but the width and overflows are not really working anymore then? You saw the css work perfectly fine in the fiddle above; it's a different issue if you have other styles interfering. Fix the CSS and don't use scripting for such trivial styling. Unfortunately my guessing powers are not enough to figure that out, maybe if you've added a jsfiddle with actual content?
Well, this is in a grid, with about 5 to an x number of columns. It is possible to resize each column individualy. This is not working with your solution. I can't drag a column smaller then the text in it if its styled with css
@ErikDekker: if you are unable to provide a jsfiddle or a link to the original than there is nothing that can be really done. Shame - could be a quick fix, and it's best to fix CSS issues with CSS ಠ_ಠ Bottom line - the best performance gain you will ever get is by offloading this logic onto the rendering engine
You have a point here. But my problem is that i'm working with a third party grid, with all kind of custom wrappers and code around it. This little loop is some custom extention to that grid to make it fully resizable. Its kinda hard to put something like that in a jsfiddle. Thats why I only asked a question about performance of that loop.
0

OK, here's another alternative:

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

2 Comments

Nope, this is just as slow as my own loop ( > 700 ms), but nice try ;-)
@ErikDekker I suspected it would be - it must be something to do with evaluating and changing the widths over and over for every element. Probably related to it causing reflows.
0

what if you use

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

And also try to use optimize selector in jQuery

1 Comment

Sorry, but this does not give me any (visible) results in performance time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.