The best way to update dom is by using createDocumentFragment.
It creates a new empty DocumentFragment into which DOM nodes can be added to build an offscreen DOM tree.
DocumentFragments are DOM Node objects which are never part of the main DOM tree. The usual use case is to create the document fragment, append elements to the document fragment and then append the document fragment to the DOM tree.
Since the document fragment is in memory and not part of the main DOM tree, appending children to it does not cause page reflow which results in better performance.
For your case:
const dataToFill = [[1,2,3], [4,5,6], [7,8,9]]
const fragment = document.createDocumentFragment()
for (const row of dataToFill){
const tr = document.createElement('tr')
for (const column of row) {
const td = document.createElement('td')
td.textContent = column
tr.appendChild(td)
}
fragment.appendChild(tr)
}
document.getElementById('my-table').appendChild(fragment)
<table id="my-table">
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
</tr>
</table>
If you will remove document.getElementById('my-table').appendChild(fragment) You won't see the table contains, which means it is getting rendered only once outside the loop. All other operations are only done in memory.
This makes it equivalent to $('table').append('<tr>...</tr>') but in a much readable way which is easy to debug.
$('table').append('<tr><td>${var}</td>...</tr>)?$('table').append(`<tr><td>${var}</td></tr>`).