开发者

Why does removing an element with javascript prevent iteration of elements?

I am trying to replace all text fields on a page with labels.

function replaceInputTextFieldsWithValues() {

    var inputFields = document.getElementsByTagName("input");

    for(var i = 0; i < inputFields.length; i++) {
        if(inputFields[i].getAttribute("type")== "text") {          
            var parent = inputFields[i].parentNode;
            var value = inputFields[i].value;
            parent开发者_Go百科.removeChild(inputFields[i]);
            var label = document.createElement('label');
            label.setAttribute('for', value);
            label.innerHTML = value;
            parent.appendChild(label);
        }
    }
}

My HTML document is organized in tables. This function only seems to work on the first element in each table.

On the other hand, when I remove the line:

parent.removeChild(inputFields[i]);

The code seems to work fine. Why is this happening and how do I fix it?


What you get back from getElementsByTagName is an HTMLCollection, which is live. (This is true for the other getElementsByXYZ methods, but not querySelectorAll.) That means if you remove the element at index 0, the HTMLCollection's length will go down and you'll have a new element at index 0 instead of the one you just removed.

Just work your way through it backward and you'll be fine:

for(var i = inputFields.length - 1; i >= 0; i--) {
    // ...
}

Alternately, convert the HTMLCollection into an array and then loop through the array. (See the live example and code below).

Edit: Or, as Chris Shouts points out in the comments, you can just make use of the changing length, but it's not quite as simple as Chris' suggestion because you're only removing the elements sometimes. It would look like this:

var inputFields = document.getElementsByTagName("input");
var i = 0;
while (i < inputFields.length) {
    if(inputFields[i].getAttribute("type")== "text") {
       // Remove it and DON'T increment `index`
    }
    else {
       // Skip this one by incrementing `index`
       ++index;
    }
}

Which of these three approaches to use will depend on the situation. Copying to an array gives you a nice static dataset to work with, and if you make sure to release the reference to the HTMLCollection, you're giving the browser the opportunity to realize it doesn't have to keep that list up-to-date when things change, which could reduce overhead. But you're copying the references briefly, which increases overhead a bit. :-)


Additional: Here's an example showing this effect, and also showing a fairly efficient (but obscure) way to create an array from a HTMLCollection:

HTML:

<ul>
  <li>LI0</li>
  <li>LI1</li>
  <li>LI2</li>
</ul>

JavaScript:

var lilist, liarray;

// Get the HTMLCollection, which is live
lilist = document.getElementsByTagName('li');

// Create an array of its elements
liarray = Array.prototype.slice.call(lilist, 0);

// Show initial length of both
display("lilist.length = " + lilist.length);   // Shows 3
display("liarray.length = " + liarray.length); // Shows 3

// Show what the 0th element of both is (both show "LI0" in the live example)
display("lilist[0].innerHTML = " + lilist[0].innerHTML);   // Shows LI0
display("liarray[0].innerHTML = " + liarray[0].innerHTML); // Shows LI0

// Remove the first list item
display("Removing item 0");
lilist[0].parentNode.removeChild(lilist[0]);

// Show the length of both, note that the list's length
// has gone down, but the array's hasn't
display("lilist.length = " + lilist.length);    // Shows 2, not 3
display("liarray.length = " + liarray.length);  // Still shows 3

// Show what the 0th element of both *now* is
display("lilist[0].innerHTML = " + lilist[0].innerHTML);   // Shows LI1 now
display("liarray[0].innerHTML = " + liarray[0].innerHTML); // Still shows LI0

Live copy

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜