JavaScript - understanding the difference between filter and splice within a forEach iteration

Understanding mutation methods

Manipulating arrays can be done using splice and filter. While filter does not change the original array, splice is a mutable method. When we apply splice to an array, it directly alters the array itself.

Presenting the issue

Now, let's explore how performing forEach on an array can lead to element removal with both splice and filter.

1. Using splice

let arr = [1, 2, 3, 4];

arr.forEach(el => {
  console.log(el);
  
  if (el === 2) {
    arr.splice(el, 1); // remove element 2
  }
});

As expected, this code snippet outputs 1, 2, 4. Modifying the arr midway through the loop disrupts the outcome.

2. Utilizing filter

let arr = [1,2,3,4];

arr.forEach(el => {
  console.log(el);

  if (el === 2) {
    arr = arr.filter(el => el !== 2); // remove element 2
  }
});

In contrast, running this piece of code produces 1, 2, 3, 4, showcasing that modifying arr during the loop has different effects than using splice.

Posing the question

Both arrays were manipulated within the loop in similar manners, yet yielded disparate results. This raises the query: why does splice alter the original array while filter does not?

Answer №1

This is the basic behavior defined for Array.prototype.splice, Array.prototype.filter, and Array.prototype.forEach. splice changes the array itself, filter creates a new array with filtered values.

Array.prototype.forEach goes through the array sequentially. If you modify the array at one index, it will move to the next on the following iteration unless it reaches the end. Changes made to the array won't affect forEach once it's running. Think of it like a regular function call:

let someNum = 8;
function example(input) {
    someNum = 10;
    console.log(input); // still shows 8
}
example(someNum);

In JavaScript, variables are passed by reference or primitive value, not as pointers like in other languages.

Answer №2

Let's clear up one point: in the initial example provided by the OP, the deletion actually affects the third element of the array, not the second as indicated by the comment. It seems that the intention was to remove the second element based on the subsequent example.

To correct this issue, we can utilize the index parameter passed to the forEach callback like so:

let arr = [1, 2, 3, 4];

arr.forEach((el, index) => {
  console.log(el);
  
  if (el === 2) {
    // deleting the actual second element instead of the one at index 2
    arr.splice(index, 1);
  }
});

Interestingly, even after fixing the semantic error, the console will still display what was mentioned by the OP:

1
2
4

This occurs despite the resulting value of arr being updated to [1, 3, 4] due to the splice() operation.

So why does this happen? The Mozilla Developer Network (MDN) provides a similar scenario regarding altering an array within a forEach loop. Essentially, the callback function passed to forEach is executed for every index of the array until it reaches the end. During the second iteration, the logic refers to index 1 and removes that index, causing elements following it to shift down one position: the value 3 moves to index 1, while 4 takes the spot at index 2. As we have already processed index 1, the third iteration occurs at index 2, now containing the value 4.

The table below illustrates this process further:

Iteration Value of el Initial arr state Final arr state
1 1 [1, 2, 3, 4] [1, 2, 3, 4]
2 2 [1, 2, 3, 4] [1, 3, 4]
3 4 [1, 3, 4] [1, 3, 4]

In essence, you can think of Array.prototype.forEach as behaving similarly to the following:

Array.prototype.forEach = function(callbackFn, thisArg) {
  for (let index = 0; index < this.length; ++index) {
    callbackFn.call(thisArg, this.at(index), index, this)
  }
}

The distinction between the two examples lies in how the objects are altered. In the first instance, the OP uses splice to directly modify the object referenced by the variable arr ("in place", per the MDN documentation). Conversely, the second example involves reassigning the variable arr to point at a new object. However, because forEach operates as a function, the original object pointed to by arr remains accessible within the closure formed by the forEach call until the function finishes. This becomes more apparent when additional logging is introduced using the third parameter passed to the callback (the array on which the callback operated).

let arr = [1,2,3,4];

arr.forEach((el, index, list) => {
  console.log("el:", el, "list:", list, "arr:", arr);

  if (el === 2) {
    arr = arr.filter(el => el !== 2); // removing element 2
  }
});

The adjusted code snippet yields the following results:

el: 1 list: [1, 2, 3, 4] arr: [1, 2, 3, 4]
el: 2 list: [1, 2, 3, 4] arr: [1, 2, 3, 4]
el: 3 list: [1, 2, 3, 4] arr: [1, 3, 4]
el: 4 list: [1, 2, 3, 4] arr: [1, 3, 4]

Notice that the value of list, obtained from the forEach closure, remains constant despite arr undergoing updates during the second traversal.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Implementing Material UI datetime-local feature with no minute selection

Is there a way to hide minutes in a TextField with type = datetime-local? <TextField label="From" type="datetime-local" InputLabelProps={{ shrink: true, }} /> This is how it appears on my end: screenshot ...

The THREE.js FBXLoader mistakenly identifies .png files as .psd files, causing issues with loading materials

While using the THREE.js FBXLoader to import a .fbx file, I noticed that the model is only partially loaded, and the alpha textured parts are not loading at all. An error message I encountered is: FBXLoader: PSD textures are not supported, creating emp ...

Having trouble with AngularJS not sending form values properly?

I have a code snippet in my app.js file where I am trying to pass the value of email to a file named handler.php. $scope.doForget={email:''}; $scope.doForget = function (customer) { //email = $scope.forget.email; For Testing i am hardcoding ...

A guide to implementing a dynamic limit in ng-repeat using AngularJS

I'm looking to dynamically set a limit in my ng-repeat. The goal is to limit the number of items displayed to 1 when the window width is less than 750px. Below is the function in my controller: $scope.$watch('window.innerWidth', function() ...

Finding the average of a column within a PHP array

I have a text file with tab-delimited data that needs to be processed. You can access the file here: I've successfully converted the data into an array using this PHP script: However, I'm struggling to figure out how to calculate the average fo ...

Infinite scrolling with a dynamic background

Hi there, I am working on my website and trying to create a smooth transition between sections similar to the one demonstrated here:. The challenge I'm facing is that the backgrounds of my sections cannot be fixed; they need to have background-attachm ...

What is the method for determining the dimensions of a cube?

Currently, I am working with a cube geometry and a mesh in my project. However, I am facing a challenge figuring out how to obtain the width of the cube. Below is the snippet of code that I am struggling with: geometry = new THREE.CubeGeometry( 200, 200 ...

Updating or adding new keys to a multidimensional array in PHP that has already been saved

This code showcases a saved array. $market_data_array = get_option('market_data'); Array ( [Turkey] => Array ( [Gold] => Array ( [2020] => Array ( ...

The series in angular-chart is not displayed at the top as shown in the documentation

angular-chart.js provides an example of a bar chart, which can be found here. Using this as a foundation, I made some modifications to the js and markup code like so. HTML <body ng-app="app"> <div class="row"> <div class="col-m ...

Simple steps for importing JS files in a web application

Upon examining the code snippet below: const express = require('express'); const app = express(); const http = require('http').Server(app); const io = require('socket.io')(http); const cors = require('cors'); app.u ...

Is it possible to solely track the speed of vertical mouse movements with cursormeter?

For the past week, I have been on a quest to find a solution using Jquery to extract only the vertical mouse speed. Although Cursormeter is useful, it does not solely focus on vertical speed: If anyone has any advice, please share! ...

Try to showcase the contents of a JSON file

Below is the JSON data that I need to reference: { "webservice_status": { "status": "SUCCESS", "message": "" }, "dailyroster": [ { "webservice_status": null, "recordNumber": "3014973", "first_date": "2016-04-27", ...

Ways to output a string array from a JSON object containing additional attributes

On the client side, I have received a JSON object from a REST service. This object contains multiple attributes, one of which is a String array. I need guidance on how to display this array using embedded AngularJS code in HTML. Here is my JSON object: [ ...

Learn the steps to Toggle Javascript on window resize

Is there a way to toggle Javascript on and off when the window is resized? Currently, resizing the window causes the navigation bar to stick and remain visible above the content. <script> if ( $(window).width() <= 1200 ) { }else{ $('nav& ...

Generate dynamic DIV elements and populate them with content

Can you assist me in getting this code to function properly? My goal is to dynamically create elements inside a div and fill each element with a value fetched from the database through a server-side function. I'm unsure if there's a better approa ...

The Ajax Form disappears from the page

After racking my brain for a while, I've come to the conclusion that it's time to seek some assistance. I have work tomorrow and I don't want to spend all night on this. The issue lies in my form which is located inside a modal, here is my ...

Unloading a dynamically-loaded child component in Vue.js from the keep-alive cache

I have a question that is similar to the one mentioned here: Vue.js - Destroy a cached component from keep alive I am working on creating a Tab System using Vue router, and my code looks something like this: //My Tab component <template> <tab& ...

Using PHP and AJAX to upload a file without the need for it to be inside a form

I have a data table where each row ends with an edit button. When the edit button is clicked, all columns in that row are converted to text areas using JavaScript, allowing the user to edit the values. A save button then appears, and upon clicking it, the ...

Rendering faces with a different texture using BufferGeometry in Three.js

Here is my output date: geom[0] = { texturesindexT: new Int16Array([0,1,2,3]), texturesindexS: new Int16Array([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,...]), materialsindexT: new Int16Array([-1,-1,-1,-1]), materialsindexS: new Int16Array([-1,0,1,2,3,4,5,0,6,2,7,8 ...

Modifying two distinct charts according to the choices made in two independent dropdown menus

In my current project, I am facing a challenge with integrating two separate dropdowns containing UFC fighter names. The goal is to display a plot showing the KD (Knockdown) data for the selected fighters over time when their names are chosen from both dro ...