Cómo funciona array.prototype.map ()

JavaScript es un lenguaje omnipresente ahora. Una vez limitado al uso del lado del cliente, ahora puede encontrarlo en servidores de muchas formas. A medida que JavaScript creció, también lo hizo su arsenal de funciones que los usuarios pueden usar. La mayoría de las veces estás satisfecho con estos métodos y solo en raras ocasiones querrás dar ese paso adicional para comprender lo que realmente está sucediendo bajo el capó.

En ese sentido, vamos a dar un paso más hoy y explorar una función muy popular: Array.prototype.map().

Descargo de responsabilidad : no explicaré cómo usarlo map(); el siguiente ejemplo lo ilustra, o puede encontrar numerosos ejemplos cuando busque en Google. En su lugar, analicemos cómo se implementa el mapa detrás de escena.

El map()método crea una nueva matriz con el resultado de llamar a una función proporcionada en cada elemento de la matriz de llamada.

Ejemplo:

var array1 = [1, 4, 9, 16]; // pass a function to map const map1 = array1.map(x => x * 2); console.log(map1); // expected output: Array [2, 8, 18, 32]

Implementación

Escojamos la implementación directamente de la boca del caballo e intentemos diseccionarla. A continuación se muestra el polyfill MDN. Dedique algún tiempo a comprender el código, cópielo y ejecútelo en su máquina. Si usted es un desarrollador de JavaScript principiante / intermedio, seguramente se encontrará con al menos un par de preguntas.

/*Array.prototype.map implementation*/ Array.prototype.map = function (callback/*, thisArg*/) { var T, A, k; if (this == null) { throw new TypeError('this is null or not defined'); } var O = Object(this); var len = O.length >>> 0; if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } if (arguments.length > 1) { T = arguments[1]; } A = new Array(len); k = 0; while (k < len) { var kValue, mappedValue; if (k in O) { kValue = O[k]; mappedValue = callback.call(T, kValue, k, O); A[k] = mappedValue; } k++; } return A; };

He destacado algunas preguntas comunes que pueden surgir en los comentarios del código a continuación.

/*Array.prototype.map implementation*/ Array.prototype.map = function (callback/*, thisArg*/) { var T, A, k; if (this == null) { throw new TypeError('this is null or not defined'); } var O = Object(this); var len = O.length >>> 0;// QUESTION 1 : What is the need for this line of code? if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } if (arguments.length > 1) { T = arguments[1]; } // QUESTION 2 :What is the need for the if condition and why are we assiging T=arguments[1]? A = new Array(len); k = 0; while (k < len) { var kValue, mappedValue; if (k in O) { kValue = O[k]; mappedValue = callback.call(T, kValue, k, O); // QUESTION 3: why do we pass T,k and O when all you need is kvalue? A[k] = mappedValue; } k++; } return A; };

Abordemos cada uno de ellos comenzando desde abajo

PREGUNTA 3: ¿Por qué pasamos T, ky O cuando todo lo que necesitas es kValue?

mappedValue = callback.call(T, kValue, k, O);

Esta es la más simple de las tres preguntas, así que la elegí para empezar. En la mayoría de los casos, pasar kValue a la devolución de llamada sería suficiente, pero:

  • ¿Qué sucede si tiene un caso de uso en el que necesita realizar una operación solo en todos los demás elementos? Bueno, necesitas un índice que sea (k) .
  • De manera similar, podría haber otros casos de uso en los que necesite que la matriz (O) esté disponible en la devolución de llamada.
  • ¿Por qué T ? Por ahora, sepa que T se está transmitiendo para mantener el contexto. Comprenderá esto mejor una vez que haya terminado con la pregunta 2.

PREGUNTA 2: ¿Cuál es la necesidad de la condición if y por qué estamos asignando T = argumentos [1]?

if (arguments.length > 1) { T = arguments[1]; }

La función de mapa en la implementación anterior tiene dos argumentos: la devolución de llamada y el opcional thisArg . La devolución de llamada es un argumento obligatorio, mientras que thisArg es opcional.

Se puede pasar lo que debería ser el valor "this" dentro de la devolución de llamada proporcionando el segundo argumento opcional. Esta es la razón por la que el código verifica si hay más de un argumento y asigna el segundo argumento opcional a una variable que se puede pasar a la devolución de llamada.

Para ilustrar mejor, digamos que tiene un requisito simulado en el que debe devolver el número / 2 si es divisible por 2, y si no es divisible por 2, debe devolver el nombre de usuario de la persona que llama. El siguiente código ilustra cómo puede hacer que esto suceda:

const myObj = { user: "John Smith" } var x = [10, 7]; let output = x.map(function (n) { if (n % 2 == 0) { return n / 2; } else { return this.user } }, myObj) // myObj is the second optional argument arguments[1] console.log(output); // [5,'John Smith'] //if you run the program without supplying myObj it would be //undefined as it cannot access myObj values console.log(output); // [ 5, undefined ]

PREGUNTA 1: ¿Cuál es la necesidad de esta línea de código?

var len = O.length >>> 0

Este me tomó un tiempo darme cuenta. Están sucediendo muchas cosas en esta línea de código. En JavaScript, tiene la capacidad de redefinir "esto" dentro de una función invocando el método usando call . También puede hacer esto usando bind o apply , pero para esta discusión sigamos con call.

const anotherObject={length:{}} const myObj = { user: "John Smith" } var x = [10, 7]; let output = x.map.call(anotherObject,function (n) { if (n % 2 == 0) {return n / 2;} else {return this.user} }, myObj)

Cuando invocas usando call,el primer parámetro sería el contexto en el que se ejecuta la función de mapa. Al enviar el parámetro, está sobrescribiendo el "esto" dentro del mapa con el "esto" de otro objeto.

Si observa, la propiedad length del otroObjeto es un objeto vacío y no un número entero. Si solo usa O.length en lugar de O.length> >> 0, resultaría en un valor indefinido. Al cambiar a cero, en realidad está convirtiendo cualquier fracción y no entero en un número entero. En este caso, el resultado se convertiría en 0.

La mayoría de los casos de uso no necesitarán esta verificación, pero puede haber un caso límite en el que sea necesario manejar este tipo de escenario. ¡Los buenos programadores que diseñaron la especificación realmente lo pensaron bien! Hablando de la especificación, en realidad puede encontrar la especificación sobre cómo debe implementarse cada función en Ecmascript aquí:

Especificación del lenguaje ECMAScript - ECMA-262 Edición 5.1

Este documento y las posibles traducciones del mismo pueden ser copiados y suministrados a terceros, y trabajos derivados que comenten…

www.ecma-international.org

La especificación ( paso 3 ) dice claramente que la longitud debe ser un entero sin signo de 32 bits. Esta es la razón por la que estamos cambiando el relleno a cero para asegurarnos de que la longitud sea un número entero, ya que el mapa en sí no requiere que este valor sea un objeto Array.

¡Eso es!

Me gustaría agradecer a un par de personas, nunca los conocí, pero tuvieron la amabilidad de tomarse un tiempo (en foros de Internet) y ayudarme a comprender algunos matices.

Salathiel Genese, Jordan Harband - ¡gracias!

Nota: Si está atascado en una línea de código diferente, no dude en poner eso en los comentarios y haré todo lo posible para aclararlo.

¡Gracias por tu tiempo y feliz codificación!