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

135

Estaba mirando algunos fragmentos de código, y encontré múltiples elementos que llamaban a una función sobre una lista de nodos con 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. ¿Alguien puede explicarme el comportamiento de la matriz vacía frente a forEach y cómo callfunciona?

Mimo
fuente

Respuestas:

203

[]es una matriz
Esta matriz no se usa 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, forEachhay 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 thises como una matriz, en el sentido de que tiene un lengthy puede acceder a sus partes como this[1]) pasará tres cosas:

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

Por último, .calles un prototipo que tienen funciones (es una función que se llama a otras funciones).
.calltomará su primer argumento y lo reemplazará thisdentro de la función regular con lo que haya pasado call, como primer argumento ( undefinedo nulllo usará windowen JS cotidiano, o será lo que haya pasado, si está 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 forEachfunción, y está cambiando thisde la matriz vacía a una lista de todas las <a>etiquetas, y para cada <a>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 descartemos los intentos de programación funcional y nos apeguemos al bucle manual, en línea, siempre, porque esta solución es hack-ish y antiestética.

Yo diría que, si bien .forEaches menos útil que sus homólogos, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), todavía sirve los propósitos cuando todo lo que realmente quiere hacer es modificar el mundo exterior (no la matriz), n-veces, mientras que tener acceso a cualquiera arr[i]o i.

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

La respuesta es en realidad bastante simple, y algo que tío Bob y Sir Crockford se enfrentarían a ambos, debido a la supervisión:

limpiarlo .

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

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

Ahora, si se pregunta si necesita hacer esto, usted mismo, la respuesta puede ser no ...
Esto es exactamente lo que hacen ... ... todas las (?) Bibliotecas con características de orden superior en estos días.
Si está usando lodash o subrayado o incluso jQuery, todos tendrán una forma de tomar un conjunto de elementos y realizar una acción n veces.
Si no estás usando algo así, entonces, por supuesto, escribe el tuyo.

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 más allá

No solo un método auxiliar slice( )/ array( )/ etc facilitará la vida de las personas que desean usar listas tal como usan las matrices (como deberían), sino también para las personas que tienen el lujo de operar en navegadores ES6 + de usuarios relativamente cercanos. futuro, o de "transpilar" en Babel hoy, tiene características de lenguaje incorporadas, que hacen innecesario este tipo de cosas.

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.
Busque los operadores Rest and Spread ; pruébelos en el sitio de BabelJS; Si su pila tecnológica está en orden, úsela en producción con Babel y un paso de compilación.


No hay una buena razón para no poder usar la transformación de no matriz a matriz ... ... simplemente no ensucie su código haciendo nada más que pegar esa misma línea fea, en todas partes.

Norguard
fuente
Ahora estoy un poco confundido porque si lo hice: console.log (esto); Siempre obtendré el objeto de ventana Y pensé que .call cambia el valor de esto
Muhammad Saleh
.call no cambiar el valor de this, por lo que usted está utilizando callcon el forEach. Si forEach se ve así forEach (fn) { var i = 0; var len = this.length; for (; i < length; i += 1) { fn( this[i], i, this ); }, al cambiar thisdiciendo, forEach.call( newArrLike )significa que usará ese nuevo objeto como this.
Norguard
2
@MuhammadSaleh .callno cambia el valor del thisinterior de lo que le pasa forEach, .callcambia el valor del this interior de forEach . Si no está seguro de por qué thisno se pasa de forEachla función a la que desea llamar, debe buscar el comportamiento thisen JavaScript. Como una adición, aplicable solamente aquí (y mapa / filtro / reducir), hay un segundo argumento para forEach, que establece thisdentro de la función arr.forEach(fn, thisInFn)o [].forEach.call(arrLike, fn, thisInFn);o uso justo .bind; arr.forEach(fn.bind(thisInFn));
Norguard
1
@thetrystero ese es exactamente el punto. []solo está allí como una matriz, para que pueda usar .callel .forEachmétodo y cambiar thisel conjunto al que desea trabajar. .callse ve así: function (thisArg, a, b, c) { var method = this; method(a, b, c); }excepto que hay magia negra interna para establecer thisdentro de methodigual a lo que pasó thisArg.
Norguard
1
en resumen ... Array.from ()?
zakius
53

El querySelectorAllmétodo devuelve a NodeList, que es similar a una matriz, pero no es exactamente una matriz. Por lo tanto, no tiene un forEachmétodo (que los objetos de matriz heredan a través de Array.prototype).

Dado que a NodeListes similar a una matriz, los métodos de matriz realmente funcionarán en ella, por lo que al usarlos [].forEach.callinvocas el Array.prototype.forEachmétodo en el contexto de la NodeList, como si hubieras podido hacerlo simplemente yourNodeList.forEach(/*...*/).

Tenga en cuenta que el literal de matriz vacía es solo un acceso directo a la versión expandida, que probablemente también verá con bastante frecuencia:

Array.prototype.forEach.call(/*...*/);
James Allardice
fuente
77
Entonces, para aclarar, ¿no hay ventaja en usar [].forEach.call(['a','b'], cb)más ['a','b'].forEach(cb)en aplicaciones cotidianas con matrices estándar, solo cuando se intenta iterar sobre estructuras similares a matrices que no tienen forEachsu propio prototipo? ¿Es eso correcto?
Matt Fletcher
2
@MattFletcher: Sí, eso es correcto. Ambos funcionarán, pero ¿por qué complicar demasiado las cosas? Simplemente llame al método directamente en la matriz misma.
James Allardice
Genial, gracias. Y no sé por qué, tal vez solo por credibilidad callejera, impresionar a las ancianas en ALDI y tal. Me quedaré con [].forEach():)
Matt Fletcher
var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) funciona bien
daslicht
@daslicht no en versiones anteriores de IE! ( sin embargo, eso es compatible forEachcon matrices, pero no con objetos NodeList)
Djave
21

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 haga esto, realice 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
});
Michael Geary
fuente
5

Se puede escribir mejor usando

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

});

Lo que hace es document.querySelectorAll('a')devolver un objeto similar a una matriz, pero no hereda del Arraytipo. Entonces llamamos al forEachmétodo desde el Array.prototypeobjeto con el contexto como el valor devuelto pordocument.querySelectorAll('a')

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

Básicamente es lo mismo que:

var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
   // whatever with the current node
});
vava
fuente
2

Quiere actualizar esta vieja pregunta:

La razón para usar [].foreach.call()para recorrer elementos en los navegadores modernos ha terminado. Podemos usar document.querySelectorAll("a").foreach()directamente.

Los objetos NodeList son colecciones de nodos, generalmente devueltos por propiedades como Node.childNodes y métodos como document.querySelectorAll ().

Aunque NodeList no es una matriz, es posible iterar sobre ella con forEach () . También se puede convertir en una matriz real usando Array.from ().

Sin embargo, algunos navegadores antiguos no han implementado NodeList.forEach () ni Array.from (). Esto se puede evitar utilizando Array.prototype.forEach (); consulte el ejemplo de este documento.

František Heča
fuente
1

Una matriz vacía tiene una propiedad forEachen su prototipo que es un objeto Function. (La matriz vacía es solo una manera fácil de obtener una referencia a la forEachfunción que tienen todos los Arrayobjetos). Los objetos de función, a su vez, tienen una callpropiedad que también es una función. Cuando invocas la función de una Función call, ejecuta la función con los argumentos dados. El primer argumento se conviertethis en la función llamada.

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

Ted Hopp
fuente
1

Solo agrega una línea:

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

¡Y voilá!

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

Disfruta :—)

Advertencia: NodeList es una clase global. No use esta recomendación si escribe una biblioteca pública. Sin embargo, es una forma muy conveniente de aumentar la autoeficacia cuando trabaja en el sitio web o en la aplicación node.js.

imos
fuente
1

Solo una solución rápida y sucia que siempre uso. No tocaría prototipos, como buena práctica. Por supuesto, hay muchas maneras de mejorar esto, pero se entiende 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}`);
});
Alfredo Maria Milano
fuente
0

[]siempre devuelve una nueva matriz, es equivalente new Array()pero se garantiza que devolverá una matriz porque Arrayel usuario podría sobrescribirla mientras []que no puede. Entonces, esta es una forma segura de obtener el prototipo de Array, luego, como se describe, callse utiliza para ejecutar la función en la lista de nodos de matriz (esto).

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

Xotic750
fuente
Mientras que [], de hecho, siempre devolverá una matriz, mientras que window.Arraypuede sobrescribirse con cualquier cosa (en todos los navegadores antiguos), también puede window.Array.prototype.forEachsobrescribirse con cualquier cosa. No hay garantía de seguridad en ninguno de los casos, en los navegadores que no admiten getters / setters o sellado / congelación de objetos (la congelación causa sus propios problemas de extensibilidad).
Norguard
Siéntase libre para editar la respuesta para añadir la información adicional respecto a la posibilidad de sobrescribir los prototypeo métodos de TI: forEach.
Xotic750
0

Norguard explicó QUÉ [].forEach.call() hace y James Allardice POR QUÉ lo hacemos: porque querySelectorAll devuelve unNodeList 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, puede agregar 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);
        }
    };
}
Mikel
fuente
0

Mucha información buena en esta página (ver respuesta + respuesta + comentario ), pero recientemente tuve la misma pregunta que el OP, y me tomó un poco de tiempo para obtener la imagen completa. Entonces, aquí hay una versión corta:

El objetivo es usar Arraymétodos en una matriz NodeListque no tenga esos métodos en sí.

Un patrón anterior cooptó los métodos de Array a través de Function.call(), y usó una matriz literal ( []) en lugar de Array.prototypeporque era más corto de escribir:

[].forEach.call(document.querySelectorAll('a'), a => {})

Un patrón más nuevo (post ECMAScript 2015) es usar Array.from():

Array.from(document.querySelectorAll('a')).forEach(a => {})
ellemenno
fuente