简体   繁体   中英

JavaScript delete array[i] — runs great in OSX but won't run in Windows?

I'm building a listbuilder app in vanilla JavaScript, and it's working on my macbook, but not on my PC.

What difference between Mac and PC am I missing?

The specific error in question is values aren't being deleted from my array in Windows. I'm not getting a Chrome or Firefox console error from Windows, but the "delete" function isn't removing the values from the array. Instead, it removes the LI from the HTML, but the targeted array value's LI reappears whenever a new entry is made and the unordered list is redrawn.

I've run the code through Firefox and Chrome on my mac and my PC, and updated the browsers as well.

The functionality I'm trying for is allowing the user to create a LI on the DOM by adding a value to an array, and then to remove both this LI and the array value by clicking on the LI.

My HTML:

    <div>
        <h2>Enter your Input!</h2><input class="feedback-input" id=
        "entryBox" placeholder="Type anything!" type="text"> <button id=
        "addEntry" onclick="addEntry()">Add your Entry</button>
    </div>

    <div id="outputBox">
        <ul id="outputList"></ul>

        <p>Click list item to remove</p>
    </div>
</div>

My JavaScript:

var output = document.getElementById("outputList");
var list = [];
var entry = {};

function addEntry() {
  input = document.getElementById("entryBox").value; //Reads value from form
  document.getElementById("entryBox").value = ""; //Clears entry box for new text
  list.push(input);
  while (outputList.firstChild) //clearing the old displayed list 
  {
    outputList.removeChild(outputList.firstChild);
  }

  for (i = 0; i < list.length; i++) //For loop to draw LIs in unordered list
  {
    var li = document.createElement('li'); //Creates li
    output.appendChild(li); //attaches li to the output list
    li.onclick = function() //provides list items removal function
    {

      function remove() {
        delete list[i]; //pulls it out of the array. Delete prefered to Splice so I can maintain original location of other array items.
        console.log(list);
      }
      remove();
      this.parentNode.removeChild(this); //pulls it out of the DOM
    };

    li.innerHTML = li.innerHTML + list[i]; //adds the entryBox value to the li
  }
}

I also put the code in a JSFiddle; the JS in the Fiddle didn't work at all, but I don't want to distract this into a JSFiddle lesson. https://jsfiddle.net/y3sjs7po/

document.getElementById("outputList"); will return null as the element doesn't exist until the page loads, so your script will fail.

This works: https://jsfiddle.net/y3sjs7po/1/ (loaded after the body)

BUT, you are also doing it wrong in the loop. You cannot capture the state of i in a closure like that. By the time the onclick event fires, i is always the last element in the array. Also, this does not refer to the element the event is from - you have to pass it in. Finally, use splice() to remove elements in this case, not delete .

Try this: https://jsfiddle.net/y3sjs7po/4/

I changed it to store object items that contain details of each stored item. Because indexes shift when items are removed, you cannot store the indexes and expect items to be at the same position. To this end, I've change this line:

list.push({
    input: input,
    li: null
});

It instead stores the input value and list element in an object for tracking, and later removal.

var li = document.createElement('li'); //Creates li
list[i].li = li;

The part that makes it work is this:

li.onclick = (function (item) {
    return function () // creates a closure over the local `item` variable in the parent scope
    {
        // ... find the item's position (changes as items are deleted) ...
        for (var i = 0; i < list.length; ++i)
            if (list[i] == item) {
                list.splice(i, 1); //pulls it out of the array
                item.li.parentNode.removeChild(item.li); //pulls it out of the DOM
                break;
            }
        console.log(list);
    };
})(list[i]); // pass in the current item

It properly wraps each item (input and element) in a closure (by passing it into a wrapper function). This can also be done using the bind() function as well.

There are lots of mistakes that aren't outright errors but are poor coding practice. James has pointed out the error due to a closure, I'll cover some other stuff.

var output = document.getElementById("outputList");

Doing DOM operations when you aren't really sure whether the element exists or not isn't a good strategy. Wherever possible, make sure the element exists first. And don't use global variables unless there's a good reason, these can all be kept local.

var list = [];
var entry = {};

function addEntry() {
  input = document.getElementById("entryBox").value; //Reads value from form

This undeclared variable becomes global when addEntry is called the first time.

  document.getElementById("entryBox").value = ""; //Clears entry box for new text
  list.push(input);
  while (outputList.firstChild) //clearing the old displayed list 

outputList is not declared or assigned a value, so it "works" because "outputList" is the ID of an element. ID's are made global variables (unless there's a conflicting declared variable or function declaration), it's considered very bad practice to rely on this behaviour.

  {
    outputList.removeChild(outputList.firstChild);
  }

  for (i = 0; i < list.length; i++) //For loop to draw LIs in unordered list
  {
    var li = document.createElement('li'); //Creates li
    output.appendChild(li); //attaches li to the output list
    li.onclick = function() //provides list items removal function
    {

You are (attempting to) create a sparse array, but the above will visit removed members. You must test if the member exists before creating an LI for it. Using forEach will help is it doesn't iterate over missing members.

[...]

Here's an alternative function. It keeps variables out of the global scope and seems to implement what you're after. I don't understand why you remove all the LIs only to add them again, but that seems to be what you want to do.

var addEntry = (function() {
  // Shared variables here
  var input,
      outputList,  // Declare it so not reliant on ID to global mapping
      list = [],
      entry = {};  // Unused?

  // Keep in a closure so only one instance exists, not one for each element
  function remove(el) {
    el.parentNode.removeChild(el);
  }

  // return the function
  return function() {
    outputList = outputList || document.getElementById('outputList');
    input = input || document.getElementById('entryBox');

    // store the value of the input
    list.push(input.value);

    // clear for next time
    input.value = '';

    // Remove LIs
    while (outputList.firstChild) {
      outputList.removeChild(outputList.firstChild);
    }

    // Add new LIs for each member of list
    for (var i=0, iLen=list.length; i<iLen; i++) {

      // list is sparse, so only add LI if i exists
      // could use forEach and save test
      if (list.hasOwnProperty(i)) {
        var li = document.createElement('li');
        li.innerHTML = list[i] + ' ' + i;

        // Assign listener with IIFE to break closure with i
        li.onclick = (function(i) {
          return function() {
            remove(this);
            delete list[i];
          };
        }(i));

        outputList.appendChild(li);
      }
    }
  }
}())

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