Memory leak in javascript, functions inside loop - javascript

I am looping over a response that I got from an ajax response, the response is an array containing 1000 objects , this response is used to created an html table with 1000 rows :
1st scenario :
for (var i in msg.myObjects) {
$('#mytablebody').append('<tr><td>' + msg.myObjects['item1'] + '</td><td>' +
msg.myObjects['item2'] + '</td><td>' + msg.myObjects['item3'] + '</td><td>' +
msg.myObjects['item4'] + '</td><td>' + msg.myObjects['item5'] + '</td><td>' +
msg.myObjects['item6'] + '</td><td>' + msg.myObjects['item7'] + '</td> .... </tr>');
}
Result => memory leak my RAM went to 2Go and my browser crashed
2nd scenario :
for (var i in msg.myObjects) {
document.getElementById('mytablebody').innerHTML = document.getElementById('mytablebody').innerHTML + '<tr><td>' +
msg.myObjects['item1'] + '</td><td>' +
msg.myObjects['item2'] + '</td><td>' + msg.myObjects['item3'] + '</td><td>' +
msg.myObjects['item4'] + '</td><td>' + msg.myObjects['item5'] + '</td><td>' +
msg.myObjects['item6'] + '</td><td>' + msg.myObjects['item7'] + '</td> .... </tr>';
}
Result => memory leak my RAM went to 800Mo and my browser crashed with a second ajax call
3rd scenario :
var stringResponse = '';
for (var i in msg.myObjects) {
stringResponse += '<tr><td>' + msg.myObjects['item1'] + '</td><td>' +
msg.myObjects['item2'] + '</td><td>' + msg.myObjects['item3'] + '</td><td>' +
msg.myObjects['item4'] + '</td><td>' + msg.myObjects['item5'] + '</td><td>' +
msg.myObjects['item6'] + '</td><td>' + msg.myObjects['item7'] + '</td> .... </tr>';
}
document.getElementById('mytablebody').innerHTML = stringResponse
Result => no memory leak
Ok until here I concluded that, first of all, .append() causes memory leaks, and second of all , you should never play with DOM elements inside a loop. But when I did the 4th scenario I concluded that the first conclusion was wrong (not exactly correct) and the second one is still correct.
4th scenario :
var stringResponse = '';
for (var i in msg.myObjects) {
stringResponse += '<tr><td>' + replaceNulls(msg.myObjects['item1']) + '</td><td>' +
msg.myObjects['item2'] + '</td><td>' + msg.myObjects['item3'] + '</td><td>' +
msg.myObjects['item4'] + '</td><td>' + msg.myObjects['item5'] + '</td><td>' +
msg.myObjects['item6'] + '</td><td>' + msg.myObjects['item7'] + '</td> .... </tr>';
}
document.getElementById('mytablebody').innerHTML = stringResponse
function replaceNulls(input) {
return input != null ? input : ''
}
Result => memory leak my RAM went to 2Go and my browser crashed
My questions are:
when we call functions that occurs outside a loop , it may causes a memory leak, why ?
How can I avoid this (without removing the function or moving its processing to inside the loop) ?

1000 table entries for modern web browsers should not cause any issue.
Here I'm adding 10,000 items into a table without ram problems.
The reason why it's fast is because I build up the list inside a detached DOM element, then attach when done.
Your problem might be just down to DOM draw issue, the browser having to redraw on all your updates.
Another thing I noticed, for (var i in msg.myObjects) depending on what your msg.myObjects contains this is not a good thing to use. If you can use modern JS,. for (const i of msg.myObjects) is better.
var table = document.querySelector("table");
var tbody = document.createElement("tbody");
function addLine (txt) {
var tr = document.createElement("tr");
var td = document.createElement("td");
tr.appendChild(td);
td.innerText = txt;
tbody.appendChild(tr);
}
for (var l = 1; l <= 10000; l += 1) {
addLine("This is Line " + l + ", and some extra text");
}
table.appendChild(tbody);
<table>
<thead><tr><th>Test</th></tr></thead>
</table>

Related

Changing the background color of 'td' inside of an ajax call

Below you see a ajax call that gets triggered when I press a button. Inside of the ajax call I append some'td' with value's to a table. On this line below: '</td><td>' + result[i].preRiskCategory + I'm trying to set the background color to a certain color when the value of preRiskCategory is above a certain number. Does anyone know if this is even possible and if so, how?
document.getElementById("searchButton").addEventListener("click", function (e) {
// vorige resultaten leegmaken.
clearTable();
if (id != "") {
$.post("/mainRiskanalysis/SearchMainRiskanalysisRisks?mainRiskanalysisId=" + id, function (result) {
for (let i = 0; i < result.length; i++) {
// Get risk-actionplan ids
getRiskActionplanIds(result[i].id);
// Looping through all ids and placing it in a string
for (j = 0; j < ids.length; j++)
idsString+=ids[j] + ", ";
//Get the names by id's
getZoneNameById(result[i].zoneId);
getEquipmentNameById(result[i].equipmentId);
getEquipmentTaskNameById(result[i].taskId);
getDangerNameById(result[i].dangerId);
getDangererousEnergieNameById(result[i].dangerousEnergiesId)
getConsequenceNameById(result[i].consequenceId);
// Getting the right table
var tbodyId = getTableId(result[i].categoryId);
$(tbodyId).append(
'<tr scope="row"><td><a class="text-primary" href="/riskanalysis/edit/' + result[i].id + '">Edit</a> | <a class="text-danger" href="/riskanalysis/delete/' + result[i].id + '">Delete</a>' +
'</td><td><a class="text-success" href="/riskActionPlan/create/' + result[i].id + '">Link actionplan</a>' +
'</td><td>' + idsString +
'</td><td style = "background-color: #C0C0C0">' + result[i].id +
'</td><td>' + zoneName +
'</td><td>' + equipmentName +
'</td><td>' + taskName +
'</td><td>' + result[i].activity +
'</td><td>' + result[i].action +
'</td><td>' + result[i].descriptionPotentialRisk +
'</td><td style="background-color: #99CC00">' + dangerName +
'</td><td>' + consequenceName +
'</td><td>' + dangerousEnergieName +
'</td><td style="background-color: #C0C0C0">' + result[i].preSeriousness +
'</td><td style="background-color: #C0C0C0">' + result[i].preProbability +
'</td><td style="background-color: #C0C0C0">' + result[i].preExposure +
'</td><td style="background-color: #C0C0C0">' + result[i].preRiskDegree +
'</td><td>' + result[i].preRiskCategory +
'</td><td>' + result[i].preventionMeasures +
'</td><td>' + result[i].safetyProcedures +
'</td><td>' + checkIfNull(result[i].postSeriousness) +
'</td><td>' + checkIfNull(result[i].postProbability) +
'</td><td>' + checkIfNull(result[i].postExposure) +
'</td><td>' + checkIfNull(result[i].postRiskDegree) +
'</td><td>' + checkIfNull(result[i].postRiskCategory) +
'</td><td>' + checkIfNull(result[i].improvementFactor) +
'</td><td>' + result[i].isOk +
'</td></tr>');
}
});
}
e.preventDefault()
});
Please let me know if you need further explaination of some sort.
Thanks!
Consider something like this:
'</td><td style="background-color: #C0C0C0">' + result[i].preRiskDegree +
'</td><td style="'+(
result[i].preRiskCategory > 100 ? 'background-color: orange;' : ''
)+'">' + result[i].preRiskCategory +
'</td><td>' + result[i].preventionMeasures +
You can concatenate a string that is either a background-color or an empty style using a ternary condition. Whitespace added for clarity.

How can I add buttons at the end of each table row?

I'm trying to insert a button in the end of each row of the table receitas but the buttons are being set at the start of the table instead of the end of each row.
function mostraTabelaTeste(aTipo, aLista) {
tb = '<table>';
tb += '<tr><th>Tipo</th><th>Nome</th><th>Tempo</th><th>Custo</th><th>Dificuldade</th></tr>';
for(let i in receitas) {
if(receitas[i].tipo==aTipo) {
tb += '<tr><td>' + receitas[i].tipo + '</td><td>' + receitas[i].nome + '</td><td> '+ receitas[i].tempo + '</td><td>' + receitas[i].custo + '</td><td>' + receitas[i].dificuldade + '</td></td><input type="button" id="remove_' + i + '" value="x"</td></tr>';
}
}
tb += '<table>';
document.getElementById(aLista).innerHTML = tb;
}
The line where I put all the properties into the table row (inside of for), I have an input which should be set at the end of the row but instead, it is going to the top of the table.
It's just a type error in the cell wrapping the button you're creating </td> instead of starting <td>
see belown snippet :
receitas = [
{tipo:"typ1",nome:"nome1",tempo:"tempo1",custo:"custo1",dificuldade:"dificuldade1"},
{tipo:"typ2",nome:"nome2",tempo:"tempo2",custo:"custo2",dificuldade:"dificuldade2"},
{tipo:"typ1",nome:"nome3",tempo:"tempo3",custo:"custo3",dificuldade:"dificuldade3"},
{tipo:"typ1",nome:"nome4",tempo:"tempo4",custo:"custo4",dificuldade:"dificuldade4"},
]
function mostraTabelaTeste(aTipo, aLista) {
tb = '<table>';
tb += '<tr><th>Tipo</th><th>Nome</th><th>Tempo</th><th>Custo</th><th>Dificuldade</th></tr>';
for (let i in receitas) {
if (receitas[i].tipo == aTipo) {
tb += '<tr><td>' + receitas[i].tipo + '</td><td>' + receitas[i].nome + '</td><td> ' + receitas[i].tempo + '</td><td>' + receitas[i].custo + '</td><td>' + receitas[i].dificuldade + '</td><td><input type="button" id="remove_' + i + '" value="x"</td></tr>';
}
}
tb += '<table>';
document.getElementById(aLista).innerHTML = tb;
}
mostraTabelaTeste("typ1","table");
<div id="table"></div>
You may need to add one more th for button column in first row. Like below:
tb += '<tr><th>Tipo</th><th>Nome</th><th>Tempo</th><th>Custo</th><th>Dificuldade</th><th> </th></tr>';

AppendChild only html

I would like to append Html as a child to a Element, i tried:
$.each(data, function(i, field){
var tbody = document.getElementsByTagName('tbody')[0];
tbody.appendChild('<tr><td>'+ field.name +'</td><td>' + field.vorname + '</td><td>' + field.geburtsdatum + '</td><td>' + field.strasse + '</td><td>' + field.plz + '</td></tr>');
});
But somehow i get this error:
Uncaught NotFoundError: Failed to execute 'appendChild' on 'Node': The new child element is null.
How can i fix this problem? Thanks
appendChild() takes a node as the argument, try jQuery's append() instead
var $tbody = $('tbody').eq(0);
$.each(data, function (i, field) {
$tbody.append('<tr><td>' + field.name + '</td><td>' + field.vorname + '</td><td>' + field.geburtsdatum + '</td><td>' + field.strasse + '</td><td>' + field.plz + '</td></tr>');
});
As Json said, it is best to create the html first then append it, so try
var array = $.map(data, function (field, i) {
return '<tr><td>' + field.name + '</td><td>' + field.vorname + '</td><td>' + field.geburtsdatum + '</td><td>' + field.strasse + '</td><td>' + field.plz + '</td></tr>';
});
$('tbody').eq(0).append(array.join(''));

Make an iteration more functional in JavaScript / JQuery

I wrote a simple bit of JavaScript to create a HTML table.
It is populated by iterating over an array
var resultSet;
for (var i = 0, i < questions.length;; i++){
...
resultSet += '<tr>' + '<td>' + i + '</td><td>' + questions[i].question + '</td><td>' + questions[i].userAnswer + '</td><td>' +
questions[i].correctAnswer + '</td>' + '</tr>';
}
So this an imperative approach. I was reading about Scala where an example to something similar would be:
questions.map(n => '<tr>' + '<td>' + '</td><td>' + questions[i].question + ...);
Which is a functional. The emphasis being on the what rather than the how.
So I am wondering how to make my JavaScript more functional?
Use of JQuery of course permitted.
Javascript has Array.forEach however with limited compatibility: While all other browsers support it, IE only supports it from IE9 on - so you might need a shim for the other IEs.
Basically you could write:
questions.forEach(function(a){
resultSet += '<tr><td>' + a.question + '</td><td>' +
a.userAnswer + '</td><td>' +
a.correctAnswer + '</td></tr>';
});
jQuery provides .each() which gives you the same functionality:
$.each(questions, function(index, value) {
/* Do your stuff*/
});
First, correct Scala will be
questions.map(q => '<tr>' + '<td>' + '</td><td>' + q.question + ...);
where q is an element of questions, not an index.
JavaScript also has map (see Browser Compatibility at the end for browsers which support this method). So it's the same:
questions.map(function(q) {
return '<tr>' + '<td>' + '</td><td>' + q.question + ...
});

Creating dynamic html with json taking too much time

I am working on preparing some dynamic html with jquery and json object. but the problem is that when my json object has around 1500 rows it takes ages to load.
is there a way to load the thing faster.
Some code.
$(jQuery.each(jsonObject.AvailableColumns, function (i, l) {
if (type == "manual") {
innerList1 += '<li newText="" valueFormat="' + l.ValueFormat + '" scaleID="' + l.ScaleID + '" scaleType="' + l.ScaleType + '" hasWeights="' + l.HasWeights + '" customColumnType="' + l.CustomColumnType + '" class="" id="li_' + controlId + '"><span id="span_' + controlId + '" title = "' + l.QuestionText + '">' + getDisplayString(l.QuestionText) + '</span><a class="actionLeft"></a></li>';
}
else if (type = "exportall") {
innerList2 += CreateLiWithSpans('li_' + controlId, l.QuestionText, true, false, l.ScaleID, l.ScaleType, l.HasWeights, l.CustomColumnType, l.ValueFormat);
}
controlId++;
}));
$("#itemList").html(innerlist1);
EDIT : createliwithspan method
function CreateLiWithSpans(id, html, isLeft, isAddAll, scaleID, scaleType, hasWeights, customColumnType, valueFormat, newText) {
var ancClass = isLeft ? 'actionRight' : 'actionLeft';
var liObject = "";
if (newText == null) {
newText = "";
}
if (isLeft) {
liObject = '<li newtext="' + newText + '" valueFormat="' + valueFormat + '" scaleID="' + scaleID + '" scaleType="' + scaleType + '" hasWeights="' + hasWeights + '" customColumnType="' + customColumnType + '" class="" id="' + id + '"><span id="span_' + id + '" title = "' + html + '">' + getDisplayString(html) + '</span><span style="margin:0 10px 0 20px;pagging:0"><input title = "' + (newText == "" ? html : newText) + '" type="text" id="' + id + 'displayText" value="' + (newText == "" ? html : newText) + '" /><span style="color:Red; width:100%;" id="' + id + 'displayTextError"></span></span><span style="float:left">' + CreateDropDown('ddl_' + id, valueFormat, hasWeights) + '</span><a class="' + ancClass + '"></a></li>';
}
else {
liObject = '<li newtext="' + newText + '" valueFormat="' + valueFormat + '" scaleID="' + scaleID + '" scaleType="' + scaleType + '" hasWeights="' + hasWeights + '" customColumnType="' + customColumnType + '" class="" id="' + id + '"><span id="span_' + id + '" title = "' + html + '">' + getDisplayString(html) + '</span><a class="' + ancClass + '"></a></li>';
}
return liObject;
}
You can use for loop instead of jQuery.each, that will be faster. Store the itemCount before the loop, and use that:
itemCount = jsonData.items.length;
for(var i = 0; i < itemCount; i++ ) {
...
You can also use use an array instead of string concatenation, like so:
var innerList = [];
... // inside the loop
innerList.push(CreateLiWithSpans('li_' + controlId, l.QuestionText, true, false, l.ScaleID, l.ScaleType, l.HasWeights, l.CustomColumnType, l.ValueFormat));
... // after the loop
$("#itemList").html(innerList.join(''));
This will be faster in IE, I'm not sure about other js engines.
These two methods will not make a significant difference, so you should try implementing a client side pagination from json. (Not by hiding and showing divs, by rendering only visible page into the DOM).
Instead of waiting for the loop to end to append your data, why not actively append the data as you process it. This will allow the user to get immediate feedback instead of waiting for the whole thing to process. Other than this, I'd stick with my original comment to page the data.
$(jQuery.each(jsonObject.AvailableColumns, function (i, l) {
if (type == "manual") {
$("#itemList").append( '<li newText="" valueFormat="' + l.ValueFormat + '" scaleID="' + l.ScaleID + '" scaleType="' + l.ScaleType + '" hasWeights="' + l.HasWeights + '" customColumnType="' + l.CustomColumnType + '" class="" id="li_' + controlId + '"><span id="span_' + controlId + '" title = "' + l.QuestionText + '">' + getDisplayString(l.QuestionText) + '</span><a class="actionLeft"></a></li>');
}
else if (type = "exportall") {
$("#itemList2").append(CreateLiWithSpans('li_' + controlId, l.QuestionText, true, false, l.ScaleID, l.ScaleType, l.HasWeights, l.CustomColumnType, l.ValueFormat));
}
controlId++;
}));
Try replacing jQuery.each with a plain old for...in loop. Using jQuery.each adds overhead that you don't need.
Don't concatenate strings inside your loop. Instead, .push them onto an array variable and use .join('') to build the string all at once at the end.
You may need to eliminate CreateLiWithSpans as a separate function in order to fully implement (2).
Changing from using jQuery.each to a standard javascript for loop should speed it up a bit. Make sure that you save the length to a variable like this though:
for(var i = 0, len = jsonObject.AvailableColumns.length; i < len; i++){
var l = jsonObject.AvailableColumns[i];
// Continue with rest of code
}
Probably won't be a huge increase but every little helps.
Also try lowering the number of function calls you make as these have added overhead (not usually an issue, but in a large loop it can help). Unless the code is shared between functions try doing it inline and see how much that speeds it up.

Categories

Resources