Me he encontrado con el siguiente código en la lista de correo es-discusion:
Array.apply(null, { length: 5 }).map(Number.call, Number);
Esto produce
[0, 1, 2, 3, 4]
¿Por qué es este el resultado del código? ¿Que esta pasando aqui?
javascript
ecmascript-5
Benjamin Gruenbaum
fuente
fuente

Array.apply(null, Array(30)).map(Number.call, Number)es más fácil de leer ya que evita fingir que un objeto simple es una matriz.Respuestas:
Comprender este "truco" requiere comprender varias cosas:
Array(5).map(...)Function.prototype.applymaneja los argumentosArraymaneja múltiples argumentosNumbermaneja la función los argumentosFunction.prototype.callhaceSon temas bastante avanzados en JavaScript, por lo que será más que bastante largo. Comenzaremos desde arriba. ¡Cinturón de seguridad!
1. ¿Por qué no solo
Array(5).map?¿Qué es una matriz realmente? Un objeto regular, que contiene claves enteras, que se asignan a valores. Tiene otras características especiales, por ejemplo, la
lengthvariable mágica , pero en esencia, es unkey => valuemapa regular , como cualquier otro objeto. Juguemos un poco con las matrices, ¿de acuerdo?Llegamos a la diferencia inherente entre el número de elementos en la matriz
arr.lengthy la cantidad dekey=>valueasignaciones que tiene la matriz, que puede ser diferente dearr.length.Expandir la matriz a través de
arr.lengthno crea nuevaskey=>valueasignaciones, por lo que no es que la matriz tenga valores indefinidos, no tiene estas claves . ¿Y qué sucede cuando intentas acceder a una propiedad inexistente? Se obtieneundefined.Ahora podemos levantar un poco la cabeza y ver por qué funciones como
arr.mapno caminar sobre estas propiedades. Siarr[3]simplemente no estuviera definido y existiera la clave, todas estas funciones de matriz simplemente lo revisarían como cualquier otro valor:Intencionalmente utilicé una llamada al método para probar aún más el punto de que la clave en sí nunca estuvo allí: la llamada
undefined.toUpperCasehabría provocado un error, pero no fue así. Para probar eso :Y ahora llegamos a mi punto: cómo
Array(N)funcionan las cosas. La Sección 15.4.2.2 describe el proceso. Hay un montón de mumbo jumbo que no nos importan, pero si logras leer entre líneas (o puedes confiar en mí en este caso, pero no), básicamente se reduce a esto:(opera bajo el supuesto (que se verifica en la especificación real) que
lenes un uint32 válido, y no cualquier número de valor)Ahora puede ver por
Array(5).map(...)qué no funcionaría: no definimoslenelementos en la matriz, no creamos laskey => valueasignaciones, simplemente modificamos lalengthpropiedad.Ahora que tenemos eso fuera del camino, veamos la segunda cosa mágica:
2. ¿Cómo
Function.prototype.applyfunciona?Lo que
applyhace es básicamente tomar una matriz y desenrollarla como argumentos de una llamada de función. Eso significa que los siguientes son más o menos lo mismo:Ahora, podemos facilitar el proceso de ver cómo
applyfunciona simplemente registrando laargumentsvariable especial:Es fácil probar mi reclamo en el penúltimo ejemplo:
(Sí, juego de palabras). La
key => valueasignación puede no haber existido en la matriz a la que pasamosapply, pero ciertamente existe en laargumentsvariable. Es la misma razón por la que funciona el último ejemplo: las claves no existen en el objeto que pasamos, pero sí existenarguments.¿Porqué es eso? Veamos la Sección 15.3.4.3 , donde
Function.prototype.applyse define. Principalmente cosas que no nos importan, pero aquí está la parte interesante:Que básicamente significa:
argArray.length. La especificación luego procede a hacer unforbucle simple sobre loslengthelementos, haciendo unlistvalor correspondiente (listes un vudú interno, pero es básicamente una matriz). En términos de código muy, muy suelto:Entonces, todo lo que necesitamos para imitar un
argArrayen este caso es un objeto con unalengthpropiedad. Y ahora podemos ver por qué los valores no están definidos, pero las claves no lo están, enarguments: Creamos laskey=>valueasignaciones.Uf, así que esto podría no haber sido más corto que la parte anterior. Pero habrá pastel cuando terminemos, ¡así que sé paciente! Sin embargo, después de la siguiente sección (que será breve, lo prometo) podemos comenzar a diseccionar la expresión. En caso de que lo haya olvidado, la pregunta era cómo funciona lo siguiente:
3. Cómo
Arraymaneja múltiples argumentos¡Entonces! Vimos lo que sucede cuando pasas un
lengthargumento aArray, pero en la expresión, pasamos varias cosas como argumentos (una matriz de 5undefined, para ser exactos). La Sección 15.4.2.1 nos dice qué hacer. El último párrafo es todo lo que nos importa, y está redactado de manera muy extraña, pero se reduce a:Tada! Obtenemos una matriz de varios valores indefinidos y devolvemos una matriz de estos valores indefinidos.
La primera parte de la expresión.
Finalmente, podemos descifrar lo siguiente:
Vimos que devuelve una matriz que contiene 5 valores indefinidos, con claves todas en existencia.
Ahora, a la segunda parte de la expresión:
Esta será la parte más fácil y no complicada, ya que no depende tanto de hacks oscuros.
4. Cómo
Numbertrata la entradaHacer
Number(something)( sección 15.7.1 ) se conviertesomethingen un número, y eso es todo. Cómo lo hace es un poco complicado, especialmente en los casos de cadenas, pero la operación se define en la sección 9.3 en caso de que le interese.5. Juegos de
Function.prototype.callcallesapplyel hermano de, definido en la sección 15.3.4.4 . En lugar de tomar una serie de argumentos, solo toma los argumentos que recibió y los pasa hacia adelante.Las cosas se ponen interesantes cuando se encadenan más de uno
call, aumentan el extraño hasta 11:Esto es bastante valioso hasta que comprenda lo que está sucediendo.
log.calles solo una función, equivalente alcallmétodo de cualquier otra función , y como tal, también tiene uncallmétodo en sí mismo:Y que hace
call? Acepta unthisArgconjunto de argumentos y llama a su función padre. Podemos definirlo a través deapply(nuevamente, código muy suelto, no funcionará):Hagamos un seguimiento de cómo sucede esto:
La parte posterior, o la
.mapde todoAún no ha terminado. Veamos qué sucede cuando proporciona una función a la mayoría de los métodos de matriz:
Si no proporcionamos un
thisargumento nosotros mismos, lo predeterminado eswindow. Tome nota del orden en el que se proporcionan los argumentos para nuestra devolución de llamada, y vamos a volver a dividirlo en 11:Whoa whoa whoa ... retrocedamos un poco. ¿Que está pasando aqui? Podemos ver en la sección 15.4.4.18 , donde
forEachse define, sucede lo siguiente:Entonces, obtenemos esto:
Ahora podemos ver cómo
.map(Number.call, Number)funciona:Lo que devuelve la transformación de
i, el índice actual, a un número.En conclusión,
La expresion
Funciona en dos partes:
La primera parte crea una matriz de 5 elementos indefinidos. El segundo pasa sobre esa matriz y toma sus índices, lo que resulta en una matriz de índices de elementos:
fuente
ahaExclamationMark.apply(null, Array(2)); //2, true. ¿Por qué vuelve2ytruerespectivamente? ¿No estás pasando un solo argumento, es decir,Array(2)aquí?apply, pero ese argumento se "salpica" en dos argumentos pasados a la función. Puedes ver eso más fácilmente en los primerosapplyejemplos. El primeroconsole.logmuestra que, de hecho, recibimos dos argumentos (los dos elementos de la matriz), y el segundoconsole.logmuestra que la matriz tiene unkey=>valuemapeo en la primera ranura (como se explica en la primera parte de la respuesta).log.apply(null, document.getElementsByTagName('script'));no es necesario para funcionar y no funciona en algunos navegadores, y[].slice.call(NodeList)convertir un NodeList en una matriz tampoco funcionará en ellos.thissolo se estableceWindowen modo no estricto.Exención de responsabilidad : Esta es una descripción muy formales del código de arriba - así es como yo sé cómo explicarlo. Para una respuesta más simple, consulte la excelente respuesta de Zirak arriba. Esta es una especificación más profunda en su cara y menos "ajá".
Varias cosas están sucediendo aquí. Vamos a dividirlo un poco.
En la primera línea, el constructor de matriz se llama como una función con
Function.prototype.apply.thisvalor esnulllo que no importa para el constructor de Array (thises el mismothisque en el contexto de acuerdo con 15.3.4.3.2.a.new Arrayse llama pasar un objeto con unalengthpropiedad, lo que hace que ese objeto sea una matriz como todo lo que importa.applydebido a la siguiente cláusula en.apply:.applyestá el paso de argumentos de 0 a.length, ya que llamar[[Get]]en{ length: 5 }con los valores de 0 a 4 rendimientosundefineddel constructor array es llamado con cinco argumentos cuyo valor esundefined(obtener una propiedad no declarada de un objeto).var arr = Array.apply(null, { length: 5 });crea una lista de cinco valores indefinidos.Nota : Observe la diferencia aquí entre
Array.apply(0,{length: 5})yArray(5), el primero crea cinco veces el tipo de valor primitivoundefinedy el segundo crea una matriz vacía de longitud 5. Específicamente, debido al.mapcomportamiento de (8.b) y específicamente[[HasProperty].Entonces, el código anterior en una especificación compatible es el mismo que:
Ahora a la segunda parte.
Array.prototype.mapllama a la función de devolución de llamada (en este casoNumber.call) en cada elemento de la matriz y utiliza elthisvalor especificado (en este caso, establece elthisvalor en `Número).Number.call) es el índice, y el primero es el valor de este.Numberse llama conthisasundefined(el valor de la matriz) y el índice como parámetro. Por lo tanto, es básicamente lo mismo que asignar cada unoundefineda su índice de matriz (ya que la llamadaNumberrealiza la conversión de tipo, en este caso de número a número sin cambiar el índice).Por lo tanto, el código anterior toma los cinco valores indefinidos y asigna cada uno a su índice en la matriz.
Es por eso que obtenemos el resultado de nuestro código.
fuente
Array.apply(null, { length: 2 })y noArray.apply(null, [2])lo que también llamaría alArrayconstructor pasar2como el valor de longitud? violínArray.apply(null,[2])es como elArray(2)que crea una matriz vacía de longitud 2 y no una matriz que contiene el valor primitivoundefineddos veces. Vea mi edición más reciente en la nota después de la primera parte, avíseme si está lo suficientemente clara y si no, aclararé sobre eso.{length: 2}falsifica una matriz con dos elementos que elArrayconstructor insertaría en la matriz recién creada. Como no hay una matriz real que acceda a los elementos no presentes, se obtiene loundefinedque luego se inserta. Buen truco :)Como dijiste, la primera parte:
crea una matriz de 5
undefinedvalores.La segunda parte está llamando a la
mapfunción de la matriz que toma 2 argumentos y devuelve una nueva matriz del mismo tamaño.El primer argumento que
maptoma es en realidad una función para aplicar en cada elemento de la matriz, se espera que sea una función que tome 3 argumentos y devuelva un valor. Por ejemplo:si pasamos la función foo como primer argumento, se llamará para cada elemento con
El segundo argumento que
maptoma se pasa a la función que pasa como primer argumento. Pero no sería a, b, ni c en caso defooque lo fuerathis.Dos ejemplos:
y otro solo para aclararlo:
¿Y qué hay de Number.call?
Number.calles una función que toma 2 argumentos e intenta analizar el segundo argumento a un número (no estoy seguro de qué hace con el primer argumento).Como el segundo argumento que
mappasa es el índice, el valor que se colocará en la nueva matriz en ese índice es igual al índice. Al igual que la funciónbazen el ejemplo anterior.Number.callintentará analizar el índice; naturalmente, devolverá el mismo valor.El segundo argumento que pasó a la
mapfunción en su código en realidad no tiene un efecto en el resultado. Corrígeme si me equivoco, por favor.fuente
Number.callno es una función especial que analiza argumentos a números. Es simplemente=== Function.prototype.call. Solo el segundo argumento, la función que se pasa comothis-valor acall, es relevante -.map(eval.call, Number),.map(String.call, Number)y.map(Function.prototype.call, Number)todos son equivalentes.Una matriz es simplemente un objeto que comprende el campo 'longitud' y algunos métodos (por ejemplo, push). Entonces arr in
var arr = { length: 5}es básicamente lo mismo que una matriz donde los campos 0..4 tienen el valor predeterminado que no está definido (es decir,arr[0] === undefinedproduce verdadero).En cuanto a la segunda parte, map, como su nombre lo indica, asigna de una matriz a una nueva. Lo hace atravesando la matriz original e invocando la función de mapeo en cada elemento.
Todo lo que queda es convencerte de que el resultado de la función de mapeo es el índice. El truco consiste en utilizar el método llamado 'call' (*) que invoca una función con la pequeña excepción de que el primer parámetro está configurado para ser el contexto 'this', y el segundo se convierte en el primer parámetro (y así sucesivamente). Casualmente, cuando se invoca la función de mapeo, el segundo parámetro es el índice.
Por último, pero no menos importante, el método que se invoca es el Número "Clase", y como sabemos en JS, una "Clase" es simplemente una función, y este (Número) espera que el primer parámetro sea el valor.
(*) encontrado en el prototipo de Function (y Number es una función).
MASHAL
fuente
[undefined, undefined, undefined, …]ynew Array(n)o{length: n}: los últimos son escasos , es decir, no tienen elementos. Esto es muy relevante paramap, y es por eso queArray.applyse utilizó lo impar .