Reily Bourne
Reily Bourne

Reputation: 5307

jQuery's .each slower on Safari than Chrome/Firefox

I have a large HTML table (1,000-1,500 rows, 40 cols wide). I have a few input and select boxes so users can filter rows. The relevant javascript/jquery (note: not entire code base is pasted in as it is not the bottleneck) attached to it looks like:

function autoRank() {
    // auto number
    rank = 0;
    $("#myTablePlayers .playerData").each(function() {
        if ($(this).css("display") != "none") {
            rank++;
            $(this).find('td').eq(colRank).text(rank);
        }
    });
}

var teamCols = $(),
    GPCols = $(),
    posCols = $(),
    ageCols = $();

$("#myTablePlayers .playerData").each(function() {
  var columns = $(this).find('td');
  teamCols = teamCols.add($(".colTeam", this));
  GPCols = GPCols.add(columns.eq(colGP));
  posCols = posCols.add(columns.eq(colPos));
  ageCols = ageCols.add(columns.eq(colAge))
});

function filterTable() {
    // Need some error checking on input not number
    minGP = $("#mingp").val()
    teams = $("#teamFilter").val().toUpperCase()
    position = $("#position").val()
    age = $("#age").val()

    $("#myTablePlayers .playerData").show();

    /* Loop through to check for teams */
    if (teams) {
      teamCols.each(function() {
        if (!this.innerHTML.toUpperCase().includes(teams)) {
          $(this).parent().hide();
        }
      });
    }

    /* Loop and check for min GP */
    GPCols.each(function() {
      if ( Number(this.innerHTML) < minGP) {
        $(this).parent().hide();
      }
    });

    /* Check for age requirement */
    if (age) {
      age = Number(age)
      ageCols.each(function() {
        thisAge = Number(this.innerHTML);
        if ( thisAge < age || thisAge >= age+1 ) {
          $(this).parent().hide();
        }
      });
    }

    /* Check the position requirement */
    if (position) {
      posCols.each(function() {
        var thisPos = this.innerHTML
        if (position == "D") {
          if (thisPos.indexOf("D") == -1) {
            $(this).parent().hide();
          }
        } else if (position == "F") {
          if (thisPos.indexOf("D") != -1) {
            $(this).parent().hide();
          }
        } else if (thisPos != position) {
          $(this).parent().hide();
        }
      });
    }

    autoRank();
}

When stripping down the code as minimal as possible, the offending code is the

var.each(function() { ...

in the filterTable() function.

When I run this on Chrome or Firefox it runs quickly (sub 1 second) and the DOM is rendered properly. When I execute on Safari it takes 30+ seconds.

Why is this and what can I do to adapt for this browser?


jQuery: 1.11.1 (same issue even after upgrading to 3.1.1).

Safari: 10.0.1
Firefox: 50
Chrome: 54.0.

Upvotes: 1

Views: 791

Answers (1)

Tomalak
Tomalak

Reputation: 338326

After removing all the repetition and unnecessary complication from your code, this is what remains:

var colRank = 0, colTeam = 1, colGP = 2, colAge = 3, colPos = 4;

function filterTable() {
    var minGP = +$("#mingp").val();
    var age = +$("#age").val();
    var teams = $("#teamFilter").val().toUpperCase();
    var position = $("#position").val();
    var rank = 0;

    $("#myTablePlayers .playerData").each(function () {
        if (
            (teams && this.cells[colTeam].textContent.toUpperCase().includes(teams)) ||
            (minGP && +this.cells[colGP].textContent < minGP) ||
            (age && (+this.cells[colAge].textContent < age || +this.cells[colAge].textContent >= age+1)) ||
            ((position === "D" || position === "F") && this.cells[colPos].textContent.indexOf(position) === -1) ||
            (!(position === "D" || position === "F") && (this.cells[colPos].textContent !== position))
        ) {
            this.cells[colRank].textContent = ++rank;
            this.style.display = "";
        } else {
            this.style.display = "none";
        }
    });
}

I've already removed almost all jQuery in favor of native DOM manipulation.

The remaining .each() could be tuned into a plain old for loop over the document.getElementById('myTablePlayers').tBodies[0].rows, if you want to squeeze out the last bit of possible performance.

Reorder the if conditions by likeliness: From the one that will typically filter out the most rows to the one that will filter out the least rows. Because JS short-circuits conditions, this way less conditions are checked overall.

Making the table display: fixed can also improve render performance at the expense of flexibility.

Finally, you can use CSS to do counters. This is probably faster than manually setting the contents of a table cell. Test for yourself.

Upvotes: 1

Related Questions