¿Qué hace [].forEach.call() hacer en JavaScript?


Estaba mirando algunos fragmentos de código, y encontré varios elementos llamando a una función sobre una lista de nodos con un forEach aplicado a una matriz vacía.

Por ejemplo tengo algo como:

[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

Pero no puedo entender cómo funciona. ¿Puede alguien explicarme el comportamiento del array vacío frente al forEach y cómo funciona el call?

Author: Manolis, 2013-04-17

9 answers

[] es una matriz.
Esta matriz no se utiliza en absoluto.

Se está poniendo en la página, porque el uso de una matriz le da acceso a prototipos de matriz, como .forEach.

Esto es más rápido que escribir Array.prototype.forEach.call(...);

A continuación, forEach es una función que toma una función como entrada...

[1,2,3].forEach(function (num) { console.log(num); });

...y para cada elemento en this (donde this es similar a una matriz, ya que tiene un length y puede acceder a sus partes como this[1]) pasará tres cosas:

  1. el elemento en el array
  2. el índice del elemento (el tercer elemento pasaría 2)
  3. una referencia al array

Por último, .call es un prototipo que tienen las funciones (es una función que se llama a otras funciones).
.call tomará su primer argumento y reemplazará this dentro de la función regular con lo que haya pasado call, ya que el primer argumento (undefined o null usará window en JS diario, o será lo que usted pasó, si en "modo estricto"). El resto de los argumentos se pasarán a la función original.

[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
    console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"

Por lo tanto, está creando una forma rápida de llamar a la función forEach, y está cambiando this de la matriz vacía a una lista de todas las etiquetas <a>, y para cada <a> en orden, está llamando a la función proporcionada.

EDITAR

Conclusión lógica / Limpieza

A continuación, hay un enlace a un artículo que sugiere que desechamos los intentos de programación funcional, y se adhieren a manual, bucle en línea, cada vez, porque esta solución es hack-ish y antiestético.

Yo diría que mientras que .forEach es menos útil que sus contrapartes, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), todavía sirve para propósitos cuando todo lo que realmente quieres hacer es modificar el mundo exterior (no el array), n-veces, mientras tienes acceso a arr[i] o i.

Entonces, ¿cómo lidiamos con la disparidad, ya que Motto es claramente un chico talentoso y conocedor, y yo me gustaría imaginar que sé lo que estoy haciendo/a dónde voy (de vez en cuando... ...otras veces es el aprendizaje de cabeza)?

La respuesta es en realidad bastante simple, y algo que el tío Bob y Sir Crockford tanto facepalm, debido a la supervisión:

límpialo..

function toArray (arrLike) { // or asArray(), or array(), or *whatever*
  return [].slice.call(arrLike);
}

var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);

Ahora, si usted está cuestionando si necesita hacer esto, usted mismo, la respuesta bien puede ser no...
Esto se hace exactamente por... ...cada(?) biblioteca con características de orden superior en estos días.
Si está utilizando lodash o underscore o incluso jQuery, todos van a tener una forma de tomar un conjunto de elementos y realizar una acción n-veces.
Si no estás usando tal cosa, entonces por todos los medios, escribe la tuya.

lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
lib.extend = function (subject) {
  var others = lib.array(arguments, 1);
  return others.reduce(appendKeys, subject);
};

Actualización para ES6(ES2015) y años posteriores

No solo es un slice( )/array( )/etc helper método va a hacer la vida más fácil para las personas que quieren utilizar listas al igual que utilizan matrices (como deberían), pero para las personas quienes tienen el lujo de operar en navegadores ES6+ en un futuro relativamente cercano, o de" transpiling " en Babel hoy, tienen características de lenguaje incorporadas, que hacen que este tipo de cosas sean innecesarias.

function countArgs (...allArgs) {
  return allArgs.length;
}

function logArgs (...allArgs) {
  return allArgs.forEach(arg => console.log(arg));
}

function extend (subject, ...others) { /* return ... */ }


var nodeArray = [ ...nodeList1, ...nodeList2 ];

Súper limpio y muy útil.
Buscar el Descanso y Propagación operadores; pruébelos en el sitio de BabelJS; si su pila de tecnología está en orden, úselos en producción con Babel y un paso de construcción.


No hay nada bueno razón para no poder usar la transformación de no-matriz en matriz... ...simplemente no desordenes tu código sin hacer nada sino pegando esa misma línea fea, en todas partes.

 155
Author: Norguard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-19 21:30:55

El querySelectorAll method devuelve un NodeList, que es similar a un array, pero no es del todo un array. Por lo tanto, no tiene un método forEach (que los objetos array heredan a través de Array.prototype).

Dado que un NodeList es similar a una matriz, los métodos de matriz realmente funcionarán en ella, por lo que al usar [].forEach.call está invocando el método Array.prototype.forEach en el contexto de NodeList, como si hubiera sido capaz de simplemente hacer yourNodeList.forEach(/*...*/).

Tenga en cuenta que el literal de matriz vacía es solo un atajo a la expandida versión, que probablemente verá muy a menudo también:

Array.prototype.forEach.call(/*...*/);
 40
Author: James Allardice,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-17 06:52:45

Las otras respuestas han explicado este código muy bien, así que solo agregaré una sugerencia.

Este es un buen ejemplo de código que debe ser refactorizado por simplicidad y claridad. En lugar de usar [].forEach.call() o Array.prototype.forEach.call() cada vez que hagas esto, haz una función simple:

function forEach( list, callback ) {
    Array.prototype.forEach.call( list, callback );
}

Ahora puede llamar a esta función en lugar del código más complicado y oscuro:

forEach( document.querySelectorAll('a'), function( el ) {
   // whatever with the current node
});
 15
Author: Michael Geary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-17 07:00:33

Se puede escribir mejor usando

Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {

});

Lo que es does es document.querySelectorAll('a') devuelve un objeto similar a un array, pero no hereda del tipo Array. Así que llamamos al método forEach desde el objeto Array.prototype con el contexto como el valor devuelto por document.querySelectorAll('a')

 4
Author: Arun P Johny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-17 07:02:49

Un array vacío tiene una propiedad forEach en su prototipo que es un objeto de función. (La matriz vacía es solo una manera fácil de obtener una referencia a la función forEach que tienen todos los objetos Array.) Los objetos Function, a su vez, tienen una propiedad call que también es una función. Cuando invoca la función call de una función, ejecuta la función con los argumentos dados. El primer argumento se convierte en this en la función llamada.

Puede encontrar documentación para la función call aquí. La documentación para forEachestá aquí.

 1
Author: Ted Hopp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-17 06:51:34

Basta con añadir una línea:

NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;

Y voila!

document.querySelectorAll('a').forEach(function(el) {
  // whatever with the current node
});

Disfruta : -)

Advertencia: NodeList es una clase global. No utilice esta recomendación si está escribiendo biblioteca pública. Sin embargo, es una forma muy conveniente para aumentar la autoeficacia cuando trabajas en un sitio web o nodo.js app.

 1
Author: imos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-24 13:17:28

Solo una solución rápida y sucia que siempre termino usando. No tocaría prototipos, como una buena práctica. Por supuesto, hay muchas maneras de mejorar esto, pero entiendes la idea.

const forEach = (array, callback) => {
  if (!array || !array.length || !callback) return
  for (var i = 0; i < array.length; i++) {
    callback(array[i], i);
  }
}

forEach(document.querySelectorAll('.a-class'), (item, index) => {
  console.log(`Item: ${item}, index: ${index}`);
});
 1
Author: Alfredo Maria Milano,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-02 05:59:44

[] siempre devuelve un nuevo array, es equivalente a new Array() pero se garantiza que devuelve un array porque Array podría ser sobrescrito por el usuario mientras que [] no puede. Así que esta es una forma segura de obtener el prototipo de Array, luego como se describe, call se usa para ejecutar la función en la lista de nodos arraylike (this).

Llama a una función con un valor dado y argumentos proporcionados individualmente. mdn

 0
Author: Xotic750,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-17 07:10:33

Norguard explicó ¿QUÉ [].forEach.call() ¿y James Allardice por QUÉ podemos hacer: porque querySelectorAll devuelve un NodeList que no tiene un método forEach...

A menos que tenga un navegador moderno como Chrome 51+, Firefox 50+, Opera 38, Safari 10.

Si no se puede añadir un Polyfill :

if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (var i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}
 0
Author: Mikel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-27 10:51:21