Splicing a JavaScript Array from Within the Callback Passed to Foreach

Splicing a Javascript array from within the callback passed to forEach

Lets see why JavaScript behaves like this. According to the ECMAScript standard specification for Array.prototype.forEach,

when you delete an element at index 1, the element at index 2 becomes the element at index 1 and index 2 doesn't exist for that object.

Now, JavaScript looks for element 2 in the object, which is not found, so it skips the function call.

That is why you see only a and b.


The actual way to do this, is to use Array.prototype.filter

var array = ["a", "b", "c"];

array = array.filter(function(currentChar) {
console.log(currentChar); // a, b, c on separate lines
return currentChar !== "b";
});
console.log(array); // [ 'a', 'c' ]

Splice on forEach loop not working properly

Your code is splicing while looping. Spliced elements are accessed even they are no more existing. That leads to undefined elements.

You may consider Array#filter

var obj = [{ "index": 0, "name": "Odonnell Noble", "gender": "male", "company": "DIGIQUE", "eail": "odonnellnoble@digique.com" }, { "index": 1, "name": "Marie Oneal", "gender": "female", "company": "CANOPOLY", "email": "marieoneal@canopoly.com" }, { "index": 2, "name": "Adrienne Marsh", "gender": "female", "company": "XOGGLE", "email": "adriennemarsh@xoggle.com" }, { "index": 3, "name": "Goff Mullins", "gender": "male", "company": "ENDIPIN", "email": "goffmullins@endipin.com" }, { "index": 4, "name": "Lucile Finley", "gender": "female", "company": "AQUASSEUR", "email": "lucilefinley@aquasseur.com" }, { "index": 5, "name": "Pitts Mcpherson", "gender": "male", "company": "QUARX", "email": "pittsmcpherson@quarx.com" }];
obj = obj.filter(function (user, index) { return (user.index !== 5 && user.index !== 2);});
document.write('<pre>' + JSON.stringify(obj, 0, 4) + '</pre>');

JavaScript splice function inside foreach loop decrements index

You splice elements from the array, which you iterated, therefore indexes in "todos" reduced. Sorry for my bad english.

var notDonedTodos = [];
angular.forEach($scope.todos, function(todo, i)
{
if(!todo.done)
{
notDonedTodos.push(todo);
}
});

$scope.todos = notDonedTodos;

JS - filter v.s. splice in forEach loop

That is simply how Array.prototype.splice, Array.prototype.filter and Array.prototype.forEach are defined. splice is meant to modify the array (and return removed values) while filter is meant to return a copy of the array with only the matching values

Array.prototype.forEach iterates over the array in ascending index order. Even though you modify the array during e.g. index 3, next iteration it'll just go to the next index, e.g. 4, unless the end of the array is already reached. Mind that once .forEach is called on an array, it'll keep working on that array. You setting arr to another value doesn't affect the array nor the state of the .forEach call. Similar to a regular function call:

let someVariable = 5;
function test(param) {
someVariable = 6;
console.log(param); // still prints 5
}
test(someVariable);

After all, JavaScript works by reference (or primitive values), you're not passing a pointer to the someVariable variable like you can do in some other languages.

Is an array.forEach with a splice the best way to remove an entry from an array of unique Ids?

var id = 99;
self.tests.some(function (elem, index) {
if (elem['userTestId'] === id)
self.tests.splice(index, 1);
return true;
});
return false;
}

Could utilise Array.some? Stops looping once you return TRUE from a callback.

Does the Array.prototype.forEach() method allow the skipping of iterations by modifying the index parameter?

The index is limited to the scope of the multiplyFives() function and won't modify the index of the array.forEach() loop.

With your code, when a new element is spliced onto the array at the next index, the array.forEach() loop will see that element in the next iteration and continue splicing the same element for the remaining length of the array.

A boolean duplicate flag is required to allow duplicating adjacent 5 values and to ignore values that were already duplicated.

Here's the correct JavaScript code with comments explaining each modified line.

const array = [1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10]
console.log('Array without duplicated 5 values')
console.log(array)

function duplicateFives(array) {
// Set a boolean flag to determine if a duplicate 5 should be made
let duplicate = true

function multiplyFives (element, index) {
if (element === 5) {
if (duplicate === true) {
// Add a duplicate 5 to the array if the current element isn't from a previous duplication
array.splice(index, 0, 5)
// Prevent the duplicated 5 from duplicating itself in the next iteration
duplicate = false
} else {
// Allow duplication for the next element with a value of 5 in the array
duplicate = true
}
}
}

array.forEach(multiplyFives)
return array
}

duplicateFives(array)
console.log('Array with duplicated 5 values')
console.log(array)

change values in array when doing foreach

The callback is passed the element, the index, and the array itself.

arr.forEach(function(part, index, theArray) {
theArray[index] = "hello world";
});

edit — as noted in a comment, the .forEach() function can take a second argument, which will be used as the value of this in each call to the callback:

arr.forEach(function(part, index) {
this[index] = "hello world";
}, arr); // use arr as this

That second example shows arr itself being set up as this in the callback.One might think that the array involved in the .forEach() call might be the default value of this, but for whatever reason it's not; this will be undefined if that second argument is not provided.

(Note: the above stuff about this does not apply if the callback is a => function, because this is never bound to anything when such functions are invoked.)

Also it's important to remember that there is a whole family of similar utilities provided on the Array prototype, and many questions pop up on Stackoverflow about one function or another such that the best solution is to simply pick a different tool. You've got:

  • forEach for doing a thing with or to every entry in an array;
  • filter for producing a new array containing only qualifying entries;
  • map for making a one-to-one new array by transforming an existing array;
  • some to check whether at least one element in an array fits some description;
  • every to check whether all entries in an array match a description;
  • find to look for a value in an array

and so on. MDN link

How can i splice current index in a foreach?

.splice() accepts a start index and an amount of items to remove, not the object you want to remove from the array.

Parameters

startIndex:int — An integer that specifies the index of the element in the array where the insertion or deletion begins. You can use a negative integer to specify a position relative to the end of the array (for example, -1 is the last element of the array).

deleteCount:uint — An integer that specifies the number of elements to be deleted. This number includes the element specified in the startIndex parameter. If you do not specify a value for the deleteCount parameter, the method deletes all of the values from the startIndex element to the last element in the array. If the value is 0, no elements are deleted.

You want to do this:

var index:int = platformsCloud.indexOf(platformCloud);
platformsCloud.splice(index, 1);

Loop (for each) over an array in JavaScript

TL;DR

  • Your best bets are usually

    • a for-of loop (ES2015+ only; spec | MDN) - simple and async-friendly
      for (const element of theArray) {
      // ...use `element`...
      }
    • forEach (ES5+ only; spec | MDN) (or its relatives some and such) - not async-friendly (but see details)
      theArray.forEach(element => {
      // ...use `element`...
      });
    • a simple old-fashioned for loop - async-friendly
      for (let index = 0; index < theArray.length; ++index) {
      const element = theArray[index];
      // ...use `element`...
      }
    • (rarely) for-in with safeguards - async-friendly
      for (const propertyName in theArray) {
      if (/*...is an array element property (see below)...*/) {
      const element = theArray[propertyName];
      // ...use `element`...
      }
      }
  • Some quick "don't"s:

    • Don't use for-in unless you use it with safeguards or are at least aware of why it might bite you.
    • Don't use map if you're not using its return value.
      (There's sadly someone out there teaching map [spec / MDN] as though it were forEach — but as I write on my blog, that's not what it's for. If you aren't using the array it creates, don't use map.)
    • Don't use forEach if the callback does asynchronous work and you want the forEach to wait until that work is done (because it won't).

But there's lots more to explore, read on...


JavaScript has powerful semantics for looping through arrays and array-like objects. I've split the answer into two parts: Options for genuine arrays, and options for things that are just array-like, such as the arguments object, other iterable objects (ES2015+), DOM collections, and so on.

Okay, let's look at our options:

For Actual Arrays

You have five options (two supported basically forever, another added by ECMAScript 5 ["ES5"], and two more added in ECMAScript 2015 ("ES2015", aka "ES6"):

  1. Use for-of (use an iterator implicitly) (ES2015+)
  2. Use forEach and related (ES5+)
  3. Use a simple for loop
  4. Use for-in correctly
  5. Use an iterator explicitly (ES2015+)

(You can see those old specs here: ES5, ES2015, but both have been superceded; the current editor's draft is always here.)

Details:

1. Use for-of (use an iterator implicitly) (ES2015+)

ES2015 added iterators and iterables to JavaScript. Arrays are iterable (so are strings, Maps, and Sets, as well as DOM collections and lists, as you'll see later). Iterable objects provide iterators for their values. The new for-of statement loops through the values returned by an iterator:

const a = ["a", "b", "c"];
for (const element of a) { // You can use `let` instead of `const` if you like
console.log(element);
}
// a
// b
// c


Related Topics



Leave a reply



Submit