Cada nueva versión de JavaScript agrega algunos extras que facilitan la programación. EcmaScript 5 agregó algunos métodos muy necesarios para el Formación
tipo de datos y, aunque puede encontrar recursos que le enseñan cómo usar estos métodos, por lo general omiten una discusión sobre su uso con otra función que no sea aburrida y personalizada..
Todos los extras de la matriz ignoran agujeros en matrices.
Los nuevos métodos de matriz agregados en ES5 generalmente se conocen como Array Extras. Facilitan el proceso de trabajar con matrices al proporcionar métodos para realizar operaciones comunes. Aquí hay una lista casi completa de los nuevos métodos:
Array.prototype.map
Array.prototype.reduce
Array.prototype.reduceRight
Array.prototype.filter
Array.prototype.forEach
Array.prototype.every
Array.prototype.some
Array.prototype.indexOf
y Array.prototype.lastIndexOf
también forman parte de esa lista, pero este tutorial solo tratará los siete métodos anteriores.
Estos métodos son bastante simples de usar. Ejecutan una función que usted suministra como su primer argumento, para cada elemento de la matriz. Normalmente, la función suministrada debe tener tres parámetros: el elemento, el índice del elemento y toda la matriz. Aquí están algunos ejemplos:
[1, 2, 3] .map (función (elem, índice, arr) return elem * elem;); // devuelve [1, 4, 9] [1, 2, 3, 4, 5] .filter (función (elem, index, arr) return elem% 2 === 0;); // devuelve [2, 4] [1, 2, 3, 4, 5] .some (function (elem, index, arr) return elem> = 3;); // devuelve true [1, 2, 3, 4, 5] .every (función (elem, index, arr) return elem> = 3;); // devuelve falso
los reducir
y reducir derecho
Los métodos tienen una lista de parámetros diferente. Como lo sugieren sus nombres, reducen una matriz a un solo valor. El valor inicial del resultado se establece de manera predeterminada en el primer elemento de la matriz, pero puede pasar un segundo argumento a estos métodos para que sirva como valor inicial.
La función de devolución de llamada para estos métodos acepta cuatro argumentos. El estado actual es el primer argumento y los argumentos restantes son el elemento, el índice y la matriz. Los siguientes fragmentos muestran el uso de estos dos métodos:
[1, 2, 3, 4, 5] .reduce (función (sum, elem, index, arr) return sum + elem;); // devuelve 15 [1, 2, 3, 4, 5] .reduce (función (sum, elem, index, arr) return sum + elem;, 10); // devuelve 25
Pero probablemente ya sabías todo esto, ¿verdad? Así que pasemos a algo con lo que quizás no estés familiarizado..
Es sorprendente que más gente no sepa esto: no tiene que crear una nueva función y pasarla a .mapa()
y amigos. Aún mejor, puede pasar funciones incorporadas, como ParseFloat
sin envoltura requerida!
["1", "2", "3", "4"]. Map (parseFloat); // devuelve [1, 2, 3, 4]
Tenga en cuenta que algunas funciones no funcionarán como se esperaba. Por ejemplo, parseInt
Acepta un radix como segundo argumento. Ahora recuerde que el índice del elemento se pasa a la función como un segundo argumento. Entonces, ¿qué será lo siguiente retorno?
["1", "2", "3", "4"]. Map (parseInt);
Exactamente: [1, NaN, NaN, NaN]
. Como explicación: se ignora la base 0; así, el primer valor se analiza como se espera. Las siguientes bases no incluyen el número pasado como primer argumento (por ejemplo, la base 2 no incluye 3), lo que lleva a Yaya
s. Así que asegúrese de verificar la red de desarrolladores de Mozilla por adelantado antes de usar una función y estará listo para comenzar.
Propina: Incluso puede usar constructores incorporados como argumentos, ya que no es necesario llamarlos con nuevo
. Como resultado, se puede hacer una conversión simple a un valor booleano usando Booleano
, Me gusta esto:
["sí", 0, "no", "", "verdadero", "falso"]. filtro (booleano); // devuelve ["sí", "no", "verdadero", "falso"]
Un par de otras funciones agradables son encodeURIComponent
, Date.parse
(tenga en cuenta que no puede utilizar el Fecha
constructor, ya que siempre devuelve la fecha actual cuando se llama sin nuevo
), Array.isArray
y JSON.parse
.
.aplicar()
Si bien el uso de funciones integradas como argumentos para los métodos de matriz puede ser una buena sintaxis, también debe recordar que puede pasar una matriz como segundo argumento de Función.prototipo.aplicar
. Esto es útil, al llamar a métodos, como Matemáticas.max
o String.fromCharCode
. Ambas funciones aceptan un número variable de argumentos, por lo que tendrá que envolverlos en una función cuando use los extras de la matriz. Así que en lugar de:
var arr = [1, 2, 4, 5, 3]; var max = arr.reduce (función (a, b) return Math.max (a, b););
Puedes escribir lo siguiente:
var arr = [1, 2, 4, 5, 3]; var max = Math.max.apply (null, arr);
Este código también viene con un buen beneficio de rendimiento. Como nota al margen: en EcmaScript 6, podrás simplemente escribir:
var arr = [1, 2, 4, 5, 3]; var max = Math.max (… arr); // ESTO ACTUALMENTE NO FUNCIONA!
Todos los extras de la matriz ignoran agujeros en matrices. Un ejemplo:
var a = ["hola",,,,, "mundo"]; // a [1] a [4] no se define var count = a.reduce (function (count) return count + 1;, 0); console.log (cuenta); // 2
Este comportamiento probablemente viene con un beneficio en el rendimiento, pero hay casos en que puede ser un verdadero dolor en el trasero. Uno de estos ejemplos podría ser cuando necesite una matriz de números aleatorios; no es posible simplemente escribir esto:
var randomNums = new Array (5) .map (Math.random);
Pero recuerda que puedes llamar a todos los constructores nativos sin nuevo
. Y otro dato útil: Función.prototipo.aplicar
No ignora los agujeros. Combinando estos, este código devuelve el resultado correcto:
var randomNums = Array.apply (null, new Array (5)). map (Math.random);
La mayoría de los anteriores son conocidos y utilizados por muchos programadores de forma regular. Lo que la mayoría de ellos no sabe (o al menos no usa) es el segundo argumento de la mayoría de los extras del arreglo (solo el reducir*
las funciones no lo soportan).
Usando el segundo argumento, puedes pasar un esta
Valor a la función. Como resultado, eres capaz de usar prototipo
-metodos Por ejemplo, filtrar una matriz con una expresión regular se convierte en una sola línea:
["foo", "barra", "baz"]. filter (RegExp.prototype.test, / ^ b /); // devuelve ["bar", "baz"]
Además, verificar si un objeto tiene ciertas propiedades se convierte en una cincha:
["foo", "isArray", "create"]. some (Object.prototype.hasOwnProperty, Object); // devuelve true (debido a Object.create)
Al final, puedes usar todos los métodos que quieras:
// vamos a hacer algo loco [función (a) devolver a * a; , función (b) return b * b * b; ] .map (Array.prototype.map, [1, 2, 3]); // devuelve [[1, 4, 9], [1, 8, 27]]
Esto se vuelve una locura cuando se usa Function.prototype.call
. Ver este:
["foo", "\ n \ tbar", "\ r \ nbaz \ t"] .map (Function.prototype.call, String.prototype.trim); // devuelve ["foo", "bar", "baz"] [true, 0, null, []]. map (Function.prototype.call, Object.prototype.toString); // devuelve ["[objeto Booleano]", "[Número de objeto]", "[objeto Nulo]", "[matriz de objetos]"]
Por supuesto, para complacer a tu geek interior, también puedes usar Function.prototype.call
como el segundo parámetro. Al hacerlo, se llama a todos los elementos de la matriz con su índice como primer argumento y toda la matriz como segundo:
[function (index, arr) // lo que quieras hacer con él]. forEach (Function.prototype.call, Function.prototype.call);
Con todo lo dicho, vamos a construir una calculadora simple. Sólo queremos apoyar a los operadores básicos (+
, -
, *
, /
), y tenemos que respetar el procedimiento del operador. Entonces, multiplicación (*
) y división (/
) deben evaluarse antes de la adición (+
) y la resta (-
).
En primer lugar, definimos una función que acepta una cadena que representa el cálculo como el primer y único argumento..
función calcular (cálculo)
En el cuerpo de la función, comenzamos a convertir el cálculo en una matriz utilizando una expresión regular. Luego, nos aseguramos de que analizamos todo el cálculo uniendo las partes usando Array.prototype.join
y comparando el resultado con el cálculo original..
var parts = computing.match (// dígitos | operadores | espacio en blanco /(?:\-?[\d\.◆+ )|[-\+\*\/◆|\s+/g); si (cálculo! == partes.join ("")) arroja un nuevo error ("no se pudo analizar el cálculo")
Después de eso, llamamos String.prototype.trim
para que cada elemento elimine el espacio en blanco. Luego, filtramos la matriz y eliminamos elementos falsey (es decir, f cadenas vacías).
parts = parts.map (Function.prototype.call, String.prototype.trim); parts = parts.filter (Boolean);
Ahora, construimos una matriz separada que contiene números analizados.
var nums = parts.map (parseFloat);
Puede pasar funciones incorporadas como
ParseFloat
sin envoltura requerida!
En este punto, la forma más fácil de continuar es una simple para
-lazo. Dentro de ella, construimos otra matriz (llamada procesada
) Con multiplicación y división ya aplicada. La idea básica es reducir cada operación a una adición, de modo que el último paso sea bastante trivial.
Comprobamos cada elemento de la números
matriz para asegurar que no es Yaya
; Si no es un número, entonces es un operador. La forma más sencilla de hacerlo es aprovechando el hecho de que, en JavaScript, NaN! == NaN
. Cuando encontramos un número, lo agregamos a la matriz de resultados. Cuando encontramos un operador, lo aplicamos. Omitimos operaciones de suma y solo cambiamos el signo del siguiente número para la resta.
La multiplicación y la división deben calcularse utilizando los dos números circundantes. Debido a que ya agregamos el número anterior a la matriz, debe eliminarse usando Array.prototype.pop
. El resultado del cálculo se adjunta a la matriz de resultados, listo para ser agregado.
var procesado = []; para (var i = 0; i < parts.length; i++) if( nums[i] === nums[i] ) processed.push( nums[i] ); else switch( parts[i] ) case "+": continue; //ignore case "-": processed.push(nums[++i] * -1); break; case "*": processed.push(processed.pop() * nums[++i]); break; case "/": processed.push(processed.pop() / nums[++i]); break; default: throw new Error("unknown operation: " + parts[i]);
El último paso es bastante sencillo: simplemente agregamos todos los números y devolvemos nuestro resultado final.
return processing.reduce (function (result, elem) return result + elem;);
La función completada debería verse así:
la función calcula (cálculo) // construye una matriz que contiene las partes individuales var partes = cálculo.match (// dígitos | operadores | espacio en blanco / (?: \ -? [\ d \.] +) | [- \ + \ * \ /] | \ s + / g); // probar si todo coincidió si (cálculo! == partes.unir ("")) arrojar nuevo Error ("no se pudo analizar el cálculo") // eliminar todas las partes de espacio en blanco = partes.map (Function.prototype). llamada, String.prototype.trim); parts = parts.filter (Boolean); // construir una matriz separada que contenga números analizados var nums = parts.map (parseFloat); // construir otra matriz con todas las operaciones reducidas a las adiciones var procesado = []; para (var i = 0; i < parts.length; i++) if( nums[i] === nums[i] ) //nums[i] isn't NaN processed.push( nums[i] ); else switch( parts[i] ) case "+": continue; //ignore case "-": processed.push(nums[++i] * -1); break; case "*": processed.push(processed.pop() * nums[++i]); break; case "/": processed.push(processed.pop() / nums[++i]); break; default: throw new Error("unknown operation: " + parts[i]); //add all numbers and return the result return processed.reduce(function(result, elem) return result + elem; );
Está bien, entonces vamos a probarlo:
calcular ("2 + 2.5 * 2") // devuelve 7 calcular ("12/6 + 4 * 3") // devuelve 14
¡Parece estar funcionando! Todavía hay algunos casos de borde que no se manejan, como los cálculos del operador o los números que contienen varios puntos. El soporte para paréntesis sería bueno, pero no nos preocuparemos por profundizar en más detalles en este sencillo ejemplo.
Si bien los extras de ES5 pueden parecer, al principio, bastante triviales, revelan bastante profundidad, una vez que les das una oportunidad. De repente, la programación funcional en JavaScript se convierte en algo más que un infierno de devolución de llamada y un código de espagueti. Darme cuenta de esto fue una verdadera revelación para mí e influyó en mi forma de escribir programas.
Por supuesto, como se vio anteriormente, siempre hay casos en los que querrá usar un bucle regular. Pero, y esa es la parte agradable, no es necesario.