¿Es posible cambiar los valores de la matriz al hacer foreach en javascript?

300

ejemplo:

var arr = ["one","two","three"];

arr.forEach(function(part){
  part = "four";
  return "four";
})

alert(arr);

La matriz todavía tiene sus valores originales, ¿hay alguna forma de tener acceso de escritura a los elementos de la matriz desde la función iterativa?

rsk82
fuente
1
Relacionado: stackoverflow.com/q/6081868/632951
Pacerier
Pruebe el mapa ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ):x=[2,3,4]; x=x.map(n=>n*2); // [4,6,8]
Justin

Respuestas:

469

La devolución de llamada se pasa al elemento, el índice y la matriz en sí.

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

editar : como se indica en un comentario, la .forEach()función puede tomar un segundo argumento, que se usará como el valor de thisen cada llamada a la devolución de llamada:

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

Ese segundo ejemplo se muestra arrconfigurado como thisen la devolución de llamada. Uno podría pensar que la matriz involucrada en la .forEach()llamada podría ser el valor predeterminado de this, pero por alguna razón no lo es; thisserá undefinedsi ese segundo argumento no se proporciona.

(Nota: lo anterior thisno se aplica si la devolución de llamada es una =>función, porque thisnunca está vinculada a nada cuando se invocan tales funciones).

También es importante recordar que hay una familia completa de utilidades similares proporcionadas en el prototipo de matriz, y en Stackoverflow surgen muchas preguntas sobre una función u otra, de modo que la mejor solución es simplemente elegir una herramienta diferente. Tienes:

  • forEach para hacer algo con o para cada entrada en una matriz;
  • filter para producir una nueva matriz que contenga solo entradas calificadas;
  • map para hacer una nueva matriz uno a uno transformando una matriz existente;
  • some para verificar si al menos un elemento de una matriz se ajusta a alguna descripción;
  • everypara verificar si todas las entradas en una matriz coinciden con una descripción;
  • find buscar un valor en una matriz

y así. Enlace MDN

Puntiagudo
fuente
34
¡Gracias! ES6: arr.forEach ((o, i, a) => a [i] = myNewVal)
DiegoRBaquero
¿por qué part(o incluso oen es6) no está definido? ¿Cómo obtener el valor iterado?
Daniil Mashkin
@DaniilMashkin partsería undefinedsi un elemento de la matriz se ha asignado undefinedexplícitamente. Las ranuras "vacías" de una matriz (una entrada de matriz a la que nunca se le ha asignado ningún valor) se omiten .forEach()y la mayoría de los otros métodos de iteración de matriz.
Puntiagudo
2
Para completar, .forEach()también toma un segundo argumento thisArg, que puede usar como thisdentro de la devolución de llamada. NOTA: este es un argumento de .forEachNO un argumento de la devolución de llamada.
Moha el todopoderoso camello
3
Para usar el thispasado como segundo argumento .forEach(), debe pasar la función de devolución de llamada utilizando la sintaxis function(), ya que el uso de la función de flecha de ES6 () => {}no vincula el contexto.
jpenna
131

Vamos a tratar de mantener la sencillez y discutir la forma en que está trabajando realmente. Tiene que ver con tipos de variables y parámetros de función.

Aquí está su código del que estamos hablando:

var arr = ["one","two","three"];

arr.forEach(function(part) {
  part = "four";
  return "four";
})

alert(arr);

En primer lugar, aquí es donde deberías leer sobre Array.prototype.forEach ():

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

En segundo lugar, hablemos brevemente sobre los tipos de valor en JavaScript.

Las primitivas (indefinido, nulo, cadena, booleano, número) almacenan un valor real.

ex: var x = 5;

Los tipos de referencia (objetos personalizados) almacenan la ubicación de la memoria del objeto.

ex: var xObj = { x : 5 };

Y tercero, cómo funcionan los parámetros de función.

En las funciones, los parámetros siempre se pasan por valor.

Debido a que arres una matriz de cadenas, es una matriz de objetos primitivos , lo que significa que se almacenan por valor.

Entonces, para su código anterior, esto significa que cada vez que forEach () itera, partes igual al mismo valor que arr[index], pero no el mismo objeto .

part = "four";cambiará la partvariable, pero lo dejará arrsolo.

El siguiente código cambiará los valores que desee:

var arr = ["one","two","three"];

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

alert(arr);

Ahora, si la matriz arrera una matriz de tipos de referencia , el siguiente código funcionará porque los tipos de referencia almacenan una ubicación de memoria de un objeto en lugar del objeto real.

var arr = [{ num : "one" }, { num : "two"}, { num : "three"}];

arr.forEach(function(part, index) {
  // part and arr[index] point to the same object
  // so changing the object that part points to changes the object that arr[index] points to

  part.num = "four";
});

alert(arr[0].num);
alert(arr[1].num);
alert(arr[2].num);

A continuación se ilustra que puede cambiar partpara apuntar a un nuevo objeto mientras deja los objetos almacenados arrsolos:

var arr = [{ num : "one" }, { num : "two"}, { num : "three"}];

arr.forEach(function(part, index) {
  // the following will not change the object that arr[index] points to because part now points at a new object
  part = 5;
});

alert(arr[0].num);
alert(arr[1].num);
alert(arr[2].num);
Dave
fuente
2
de hecho, ¡gran explicación! Hubiera sido aún mejor extender la explicación en los otros métodos de iteración, para ... de, map, ... El nuevo para ... de funciona de una manera muy similar a forEach para tales casos, excepto que el " falta el índice "para alterar eventualmente la matriz original (como en forEach). Además, el mapa ES5 parece similar a forEach, tal vez solo porque la posibilidad de devolver valores de un mapa aclara las cosas desde una perspectiva de sintaxis que usar el índice.
Christophe Vidal
1
Gran explicación Gracias. Después de todo, no puedo obtener esta declaración: In functions, parameters are always passed by value. ¿Qué pasa con el segundo ejemplo?
Alex
@ 7 lados, esa declaración describe que los parámetros de la función siempre se pasarán por valor. Entonces, para las primitivas, será el valor al que apunta la primitiva. Para los objetos, será la ubicación a la que apunta el objeto. Esta página de w3schools tiene una buena explicación. Consulte las secciones Los argumentos se pasan por valor y los objetos se pasan por referencia .
Dave
En las funciones, los parámetros siempre se pasan por valor. BAM Gracias. +1
radarbob
92

Matriz: [1, 2, 3, 4]
Resultado:["foo1", "foo2", "foo3", "foo4"]

Array.prototype.map() Mantener matriz original

const originalArr = ["Iron", "Super", "Ant", "Aqua"];
const modifiedArr = originalArr.map(name => `${name}man`);

console.log( "Original: %s", originalArr );
console.log( "Modified: %s", modifiedArr );

Array.prototype.forEach() Anular matriz original

const originalArr = ["Iron", "Super", "Ant", "Aqua"];
originalArr.forEach((name, index) => originalArr[index] = `${name}man`);

console.log( "Overridden: %s", originalArr );

Roko C. Buljan
fuente
3
"Array.prototype.map () modifica la matriz original, lograda reasignando la variable arr" .map () nunca modifica la matriz original, crea una nueva. La reasignación de la variable no cambia el objeto original.
vadkou
1
let arr1 = ["1", 2, 3, 4]; arr1.map(function(v) { return "foo"+ v; }); console.log( arr ); Array.prototype.map () Nunca modifique la matriz original, por cada mutación.
Anupam Maurya
@AnupamMaurya Eso no es cierto en absoluto. mapdefinitivamente puede mutar su matriz y forEach, en general, no lo hace.
user4642212
@SebastianSimon, gracias por la respuesta. Todo lo que quiero agregar, forEach anula los datos, pero un mapa no puede, crea una nueva copia.
Anupam Maurya
14

Javascript es pasar por valor, y lo que esencialmente significa partes una copia del valor en la matriz.

Para cambiar el valor, acceda a la matriz en su bucle.

arr[index] = 'new value';

hvgotcodes
fuente
Depende del tipo de partsi se copia o no, aunque tiene razón en que la variable no es un puntero, sino un valor
Bergi
8
Decir "JavaScript es pasar por valor" es una generalización general. Hay tipos de referencia en JavaScript. Los tipos de valor se pasan por valor.
Alex Turpin
99
No es una generalización general. Una declaración completamente correcta y absoluta. Javascript pasa referencias por valor. Javascript es pasar por valor, siempre.
hvgotcodes
1
@ Bergi: No, no importa el tipo. Todos los valores se copian en la asignación: los únicos valores en JavaScript son primitivas y referencias. Tanto las primitivas como las referencias se copian en la asignación.
newacct
66
@Bergi solo porque un idioma tenga una cosa llamada referencia, no significa que use pasar por referencia. Las referencias se pueden pasar (y se pasan) por valor, es decir, el valor de la referencia se copia y esa copia se usa como argumento.
hvgotcodes
4

reemplácelo con el índice de la matriz.

array[index] = new_value;
Asif Alamgir
fuente
2
Genial ... Siempre agregue alguna explicación
devpro
4

Aquí hay una respuesta similar usando una =>función de estilo:

var data = [1,2,3,4];
data.forEach( (item, i, self) => self[i] = item + 10 );

da el resultado:

[11,12,13,14]

El selfparámetro no es estrictamente necesario con la función de estilo de flecha, por lo que

data.forEach( (item,i) => data[i] = item + 10);

También funciona.

J. Peterson
fuente
3

La función .forEach puede tener una función de devolución de llamada (eachelement, elementIndex) Entonces, básicamente, lo que debe hacer es:

arr.forEach(function(element,index){
    arr[index] = "four";   //set the value  
});
console.log(arr); //the array has been overwritten.

O si desea conservar la matriz original, puede hacer una copia antes de realizar el proceso anterior. Para hacer una copia, puede usar:

var copy = arr.slice();
zhujy_8833
fuente
3
Si desea hacer una copia de la matriz, use en map()lugar de forEach(). map()itera sobre la matriz de origen y devuelve una nueva matriz que contiene la copia [modificada] del original: la matriz de origen no se modifica.
Nicholas Carey
2

Con los métodos de objeto Array puede modificar el contenido de Array comparado con el básico para bucles, estos métodos carecen de una funcionalidad importante. No puede modificar el índice en la ejecución.

Por ejemplo, si elimina el elemento actual y lo coloca en otra posición de índice dentro de la misma matriz, puede hacerlo fácilmente. Si mueve el elemento actual a una posición anterior, no hay problema en la próxima iteración, obtendrá el mismo elemento siguiente como si no hubiera hecho nada.

Considere este código donde movemos el elemento en la posición de índice 5 a la posición de índice 2 una vez que el índice cuenta hasta 5.

var ar = [0,1,2,3,4,5,6,7,8,9];
ar.forEach((e,i,a) => {
i == 5 && a.splice(2,0,a.splice(i,1)[0])
console.log(i,e);
}); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 5 5 - 6 6 - 7 7 - 8 8 - 9 9

Sin embargo, si movemos el elemento actual a algún lugar más allá de la posición actual del índice, las cosas se complicarán un poco. Luego, el siguiente elemento cambiará a la posición de elementos movidos y en la próxima iteración no podremos verlo ni evaluarlo.

Considere este código donde movemos el elemento en la posición de índice 5 a la posición de índice 7 una vez que el índice cuenta hasta 5.

var a = [0,1,2,3,4,5,6,7,8,9];
a.forEach((e,i,a) => {
i == 5 && a.splice(7,0,a.splice(i,1)[0])
console.log(i,e);
}); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 5 5 - 6 7 - 7 5 - 8 8 - 9 9

Entonces nunca hemos cumplido 6 en el ciclo. Normalmente, en un bucle for, se espera que disminuya el valor del índice cuando mueve el elemento de la matriz hacia adelante para que su índice permanezca en la misma posición en la próxima ejecución y aún pueda evaluar el elemento desplazado al lugar del elemento eliminado. Esto no es posible con los métodos de matriz. No puedes alterar el índice. Comprueba el siguiente código

var a = [0,1,2,3,4,5,6,7,8,9];
a.forEach((e,i,a) => {
i == 5 && (a.splice(7,0,a.splice(i,1)[0]), i--);
console.log(i,e);
}); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 4 5 - 6 7 - 7 5 - 8 8 - 9 9

Como ve cuando disminuimos i, no continuará desde 5 sino desde 6, desde donde se dejó.

Así que ten esto en cuenta.

Redu
fuente
1

Para agregar o eliminar elementos completamente que alterarían el índice, mediante la extensión de la sugerencia zhujy_8833 de slice () para iterar sobre una copia, simplemente cuente el número de elementos que ya ha eliminado o agregado y modifique el índice en consecuencia. Por ejemplo, para eliminar elementos:

let values = ["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8"];
let count = 0;
values.slice().forEach((value, index) => {
    if (value === "A2" || value === "A5") {
        values.splice(index - count++, 1);
    };
});
console.log(values);

// Expected: [ 'A0', 'A1', 'A3', 'A4', 'A6', 'A7', 'A8' ]

Para insertar elementos antes:

if (value === "A0" || value === "A6" || value === "A8") {
    values.splice(index - count--, 0, 'newVal');
};

// Expected: ['newVal', A0, 'A1', 'A2', 'A3', 'A4', 'A5', 'newVal', 'A6', 'A7', 'newVal', 'A8' ]

Para insertar elementos después de:

if (value === "A0" || value === "A6" || value === "A8") {
    values.splice(index - --count, 0, 'newVal');
};

// Expected: ['A0', 'newVal', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'newVal', 'A7', 'A8', 'newVal']

Para reemplazar un elemento:

if (value === "A3" || value === "A4" || value === "A7") {
    values.splice(index, 1, 'newVal');
};

// Expected: [ 'A0', 'A1', 'A2', 'newVal', 'newVal', 'A5', 'A6', 'newVal', 'A8' ]

Nota: si implementa las inserciones 'before' y 'after', el código debe manejar las inserciones 'before' primero, al revés no sería lo esperado

Leigh Mathieson
fuente
0

Puedes probar esto si quieres anular

var newArray= [444,555,666];
var oldArray =[11,22,33];
oldArray.forEach((name, index) => oldArray [index] = newArray[index]);
console.log(newArray);
Shuhad Zaman
fuente