Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optimizing javascript performance with many event listeners

I am currently building a site that allows searching for elements, with results added to a big table (think hundreds of results). Each element in the result table can be manipulated in various ways (custom javascript 0-5 star rating, toggling a fold-out panel for additional options etc.).

If I use the site on my android tablet, performance of the javascript part is very sluggish, so I am wondering how I can improve performance.

One option I have considered is to not bind any event listeners on the result rows except for a single mouse-enter event, and then binding the actual event listeners only when the mouse is over a given element.

Any other ideas to improve performance would be greatly appreciated.

Most of my javascript code is jquery based, so if you have any jquery specific optimizations I would appreciate that also.

like image 620
cornergraf Avatar asked Dec 14 '22 16:12

cornergraf


1 Answers

You might look into javaScript event delegation. There are many answers on SO (an example here) and many good articles ( here or here ).

Basically the idea you had is actually a good solution. So, instead of binding - let's say - one hundred rows with their own event handlers, you bind only their common parent which will fire an event when any of its child will receive a mouse input.

Roughly speaking instead of this:

$('tr').on("click", function() {});

You will do this:

$('table').on('click', 'tr', function() {});

This is obviously a very simplified example, but it should be enough to build a good pattern upon.

As a side note, it's a very interesting thing (well, at least for me...) to inspect the event that is fired doing something like:

$('table').on('click', 'tr', function(evt) {
   console.log(evt);
});

And see how much information an event carries, and how much useful information you get out of the box with a simple click or mouse enter.

VanillaJs

Of course the same result can be achieved without any library.

A simple implementation using Vanilla JS can be taken from David Walsh's article linked at the beginning of the answer, it goes roughly like this:

// Get the element, add a click listener...
document.getElementById("#myTable").addEventListener("click", function(e) {
    // e.target is the clicked element.
    // Check if it was a <tr>...
    if(e.target && e.target.nodeName == "TR") {
        // Element has been found, do something with it
    }
});

If you try this code though chances are that the actual target.element is the <td>, and not the <tr> we want. This means we have to be a bit more clever and walk the DOM upwards to see if the clicked element (<td>) is contained in the one we really want (<tr>).

A rough implementation to get you started would look like this:

function walk(startEl, stopEl, targetNodeName) {
  if (!startEl || startEl === stopEl || startEl.nodeName === 'BODY') {
    return false;
  }

  if (startEl.nodeName === targetNodeName) {
    // Found it, return the element
    return startEl;
  }

  // Keep on looking...
  return walk(startEl.parentNode, stopEl, targetNodeName);
}

const container = document.getElementById('myTable');

container.addEventListener('click', (e) => {
  const clickedElm = walk(e.target, container, 'TR');
  console.log(clickedElm);
  if (clickedElm) {
    clickedElm.classList.add('clicked');
  }
})

See this fiddle or the snippet below:

function walk(startEl, stopEl, targetNodeName) {
  if (!startEl || startEl === stopEl || startEl.nodeName === 'BODY') {
    return false;
  }

  if (startEl.nodeName === targetNodeName) {
    return startEl;
  }

  return walk(startEl.parentNode, stopEl, targetNodeName);
}

const elem = document.getElementById('myTable');

elem.addEventListener('click', (e) => {
  const clickedElm = walk(e.target, elem, 'TR');
  console.log(clickedElm);
  if (clickedElm) {
    clickedElm.classList.add('clicked');
  }
})
table {
  width: 100%;
}
tr {
  background: #ddd;
}
.clicked {
  background: teal;
}
 <table id="myTable">
   <tr>
     <td>one</td>
   </tr>
   <tr>
     <td>two</td>
   </tr> 
   <tr>
     <td>three</td>
   </tr> 
   <tr>
     <td>four</td>
   </tr> 
   <tr>
     <td>five</td>
   </tr> 
   <tr>
     <td>six</td>
   </tr>    
 </table>
like image 146
Aurelio Avatar answered Jan 16 '23 17:01

Aurelio