简体   繁体   中英

Strange FOR loop behaviour in Javascript

I'm trying to generate a table with 3 rows and 3 cells eg

<table>
<tr>
   <td> 00 </td>    <td> 01 </td>    <td> 02 </td>
</tr>
<tr>
   <td> 10 </td>    <td> 11 </td>    <td> 12 </td>
</tr>
<tr>
   <td> 20 </td>    <td> 21 </td>    <td> 22 </td>
</tr>
</table>

The Javascript I'm using to do this is:

tab = document.getElementById('tab');
for(i=0; i<3; i++)
{ 
 tab.innerHTML += '<tr>';

  for(j=0; j<3; j++) 
  {
   tab.innerHTML += '<td id="'+i+''+j+'">'+i+''+j+'</td>'; 
  }

 tab.innerHTML += '</tr>';
}

The HTML being:

<table id="tab">
</table>

However, when I run the code all the table elements end up in the first column, so instead of a 3x3 table I get a 1x9 table.

Anyone know what I'm doing wrong?

You're treating the DOM like it's the HTML you write.

When you do

.innerHTML += "<tr>"

..you're creating an entire <tr></tr> element. There's no such thing as "half an element" in the DOM.


Then after that, when you += the <td> elements, they're now going after the <tr> you just created, which is invalid, so the browser corrects it as best it can.

// This is going after the <tr></tr>
tab.innerHTML += '<td id="'+i+''+j+'">'+i+''+j+'</td>'; 

And finally, when you do

.innerHTML += "</tr>"

you're simply adding invalid HTML, so it's probably ignored.


Furthermore, the use of .innerHTML += "...content..." is pretty bad. It first serializes the previous DOM nodes to HTML, destroys those DOM nodes, adds the new HTML to what it just serialized, and then parses that HTML to create (and recreate) the nodes.

So every iteration, you're destroying and recreating all that you created in all the iterations up to that point. This destruction is terribly inefficient and is unnecessary. Instead, build the entire string, and then use .insertAdjacentHTML() .

tab = document.getElementById('tab');

var html = ""
for(i=0; i<3; i++)
{ 
 html += '<tr>';

  for(j=0; j<3; j++) 
  {
   html += '<td id="'+i+''+j+'">'+i+''+j+'</td>'; 
  }

 html += '</tr>';
}

tab.insertAdjacentHTML("beforeend", html);

Option #1

You should use helper string variable for this task:

var tab = document.getElementById('tab'),
    str = '';

for (var i = 0; i < 3; i++) {
    str += '<tr>';

    for (var j = 0; j < 3; j++) {
        str += '<td id="' + i + '' + j + '">' + i + '' + j + '</td>';
    }

    str += '</tr>';
}

tab.innerHTML = str;

Demo: http://jsfiddle.net/hrrKg/

Option #2

I would also recommend to make use of table API

var tab = document.getElementById('tab');

for (var i = 0; i < 3; i++) {
    var row = tab.insertRow(i);
    for (var j = 0; j < 3; j++) {
        var cell = row.insertCell(j);
        cell.id = i + '' + j;
        cell.innerHTML = i + '' + j;
    }
}

Demo: http://jsfiddle.net/hrrKg/1/


Basically why it happens. Whenever you set something like:

tab.innerHTML += '<tr>'

table content becomes not valid HTML so browser fixes it automatically making it look like something:

<tbody><tr></tr></tbody>

Further loops makes it even more confused hence a result.

Also take a look at cookie monster 's answer, for it is more comprehensive to my mind.

Alternative to string :

tab = document.getElementById('tab');
for(i=0; i<3; i++)
{ 
 var tr = document.createElement("tr");

  for(j=0; j<3; j++) 
  {
    var td = document.createElement("td");
    td.id = i+''+j;
    td.innerHTML= i+''+j
    tr.appendChild(td); 
  }

 tab.appendChild(tr);
}

FIDDLE

Problems you are adding <tr> once which is invalid HTML by itself and gets removed automatically. You can do:

tab = document.getElementById('tab');
for(i=0; i<3; i++)
{ 
 var html = '<tr>';

  for(j=0; j<3; j++) 
  {
   html += '<td id="'+i+''+j+'">'+i+''+j+'</td>'; 
  }

 tab.innerHTML += html + '</tr>';
}

I made some small adjustments and got it working in fiddler:

tab = document.getElementById('tab');

var result = "<tbody>";
for(i=0; i<3; i++)
{ 
 result += '<tr>';

  for(j=0; j<3; j++) 
  {
   result += '<td id="'+i+''+j+'">'+i+''+j+'</td>'; 
  }

 result += '</tr>'; 
}
result += "</tbody>";

console.log(result)
tab.innerHTML = result;

In the same vein as MamaWalter's answer, here is an alternative functional approach that uses DOM nodes rather than string concatenation:

var tab = document.getElementById('tab');

var bind = Function.prototype.bind,
    $tr = bind.call(Document.prototype.createElement, document, "tr"),
    $td = bind.call(Document.prototype.createElement, document, "td"),
    $text = bind.call(Document.prototype.createTextNode, document),
    appender = function(parent, child) {
      parent.appendChild(child);
      return parent;
    },
    wrapWith = function(fn, child) { return appender(fn(), child); },
    wrapWithTd = wrapWith.bind(undefined, $td);

var outer = [1,2,3], inner = [1,2,3];

outer.map(function(row) {
  function createCellContents(col) {
    return $text(row+ '' + col);
  }

  return inner
    .map(createCellContents)
    .map(wrapWithTd)
    .reduce(appender, $tr());
}).reduce(appender, tab);

jsbin for reference.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM