Reputation: 11
I have a simple table with 10000 cells (100 rows and 100 columns), each of them has click event listener. I didn't notice any visual performance issues. Could you please explain me in details, why it's so bad for performance?
P.S. I know about event bubbling and that it's better to add one event listener on <table>
:)
const clickHandler = (event) => {
console.log(event);
};
const getTable = (rowsNum, columnsNum) => {
let row = '';
let rows = '';
for(let i = 0; i < columnsNum; ++i) {
row += '<td onclick="clickHandler(event)"></td>';
}
for(let i = 0; i < rowsNum; ++i) {
rows += `<tr>${row}</tr>`;
}
return `<table>${rows}</table>`
};
const innerTable = () => {
document.querySelector('.table').innerHTML = getTable(100, 100);
};
innerTable();
table, td {
border: 1px solid black;
}
td {
width: 5px;
height: 5px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="table">
</div>
<script src="script.js"></script>
</body>
</html>
Upvotes: 0
Views: 730
Reputation: 1074138
...why it's so bad for performance?
When you do this:
<td onclick="clickHandler(event)"></td>
Youre making the browser generate a function for each and every td
, because it has to generate a wrapper function for the code in that string attribute. That is a waste of both time and memory.
If, instead, you reuse the handler:
const table = document.querySelector('.table');
table.innerHTML = getTable(100, 100);
table.querySelectorAll("td").forEach(td => {
td.addEventListener("click", clickHandler);
});
it's markedly faster:
const clickHandler = (event) => {
console.log(event.type);
};
const getTable = (rowsNum, columnsNum) => {
let row = '';
let rows = '';
for(let i = 0; i < columnsNum; ++i) {
row += '<td></td>';
}
for(let i = 0; i < rowsNum; ++i) {
rows += `<tr>${row}</tr>`;
}
return `<table>${rows}</table>`
};
const innerTable = () => {
const table = document.querySelector('.table');
table.innerHTML = getTable(100, 100);
table.querySelectorAll("td").forEach(td => {
td.addEventListener("click", clickHandler);
});
};
innerTable();
table, td {
border: 1px solid black;
}
td {
width: 5px;
height: 5px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="table">
</div>
<script src="script.js"></script>
</body>
</html>
If you take it further and use event delegation, you don't even need the loop calling addEventListener
(so it'll be slightly faster to set up) and then if you add and remove table cells, they'll just get handled automatically:
const table = document.querySelector('.table');
table.innerHTML = getTable(100, 100);
table.addEventListener("click", clickHandler);
and
const clickHandler = (event) => {
const td = event.target.closest("td");
if (!td) {
return;
}
console.log(event.type);
};
const clickHandler = (event) => {
const td = event.target.closest("td");
if (!td) {
return;
}
console.log(event.type);
};
const getTable = (rowsNum, columnsNum) => {
let row = '';
let rows = '';
for(let i = 0; i < columnsNum; ++i) {
row += '<td></td>';
}
for(let i = 0; i < rowsNum; ++i) {
rows += `<tr>${row}</tr>`;
}
return `<table>${rows}</table>`
};
const innerTable = () => {
const table = document.querySelector('.table');
table.innerHTML = getTable(100, 100);
table.addEventListener("click", clickHandler);
};
innerTable();
table, td {
border: 1px solid black;
}
td {
width: 5px;
height: 5px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="table">
</div>
<script src="script.js"></script>
</body>
</html>
Upvotes: 1
Reputation: 251
One event handler is going to be less for the DOM and yourself to manage, which will be easier for you and more performant for the browser.
Event delegation is the correct naming for the pattern. It refers to the process of using bubbling to handle events at a higher level in the DOM. Using one event listener and filtering out when you actually want it to trigger can be easier to manage.
If you forget to remove an event listener you could end up with memory leaks.
David Walsh offers a nice post about how to utilise event delegation. https://davidwalsh.name/event-delegate
Upvotes: 1