Logo Studenta

Programación -Devolucion de llamada

¡Este material tiene más páginas!

Vista previa del material en texto

Un concepto muy importante en Javascript es la capacidad de pasar una función como argumento a otra función. Estas funciones se denominan callbacks. Estas funciones pueden llamarse en cualquier momento y pasar argumentos dentro de la función. Pronto descubriremos por qué las devoluciones de llamada son tan importantes para Javascript. La convención es usar cb como argumento para la variable que se usará de callback.
function decirHolaAlUsuario(usuario) {
 return "Hola " + usuario + "!";
}
function decirAdiosAlUsuario(usuario) {
 return "Adiós " + usuario + "!";
}
function crearSaludo(usuario, cb) {
 return cb(usuario);
}
crearSaludo("Dan", decirHolaAlUsuario); // 'Hello Dan!'
crearSaludo("Dan", decirAdiosAlUsuario); // 'Goodbye Dan!'
Más métodos de arreglos
Ya conocemos y utilizamos métodos de matriz, .push, .pop, .shift, .unshifty .length. Pero hay muchos más métodos disponibles de forma nativa en una matriz. Los métodos de los que vamos a hablar aquí se denominan "métodos de orden superior", porque toman los callbacks como argumentos.
.forEach
.forEach es un bucle para integrado en cada matriz. .forEach toma un callback como su único argumento, e itera sobre cada elemento de la matriz y llama al callback en él. El callback puede tomar dos argumentos, el primero es el elemento en sí, el segundo es el índice del elemento (este argumento es opcional).
const autos = ["Ford", "Chevrolet", "Toyota", "Tesla"];
// Podemos escribir el callback en los paréntesis como una función anónima
autos.forEach(function (elemento, indice) {
 console.log(elemento);
});
// O podemos crear una instancia de una función para usarla como callback.
// Además, no necesitamos usar el argumento de índice, si no lo necesitas, no dudes en omitirlo.
function mostrarNombres(elemento) {
 console.log(elemento);
}
// And call that function in the forEach parentheses
autos.forEach(mostrarNombres);
.reduce
.reduce ejecutará un bucle en nuestra matriz con la intención de reducir cada elemento en un elemento que se devuelve. Como es el primer argumento, acepta un callback que toma dos argumentos, primero un 'acumulador' (el resultado del método de reducción hasta ahora), y el segundo es el elemento en el que se encuentra actualmente. El callback debe contener siempre una declaración de devolución ("return"). .reduce también toma un segundo argumento opcional, que sería el acumulador de arranque. Si no se suministra el acumulador de arranque, la reducción comenzó en el primer elemento de la matriz. .reduce siempre devolverá el acumulado cuando termine de recorrer los elementos.
const numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const palabras = ["Hola,", "mi", "nombre", "es", "Martin"];
// Podemos escribir la función anónima directamente en los paréntesis de .reduce
// Si omitimos el elemento inicial, siempre comenzará en el primer elemento.
const suma = numeros.reduce(function (acc, elemento) {
 return acc + elemento;
});
// Podemos escribir una función fuera de los parents de .reduce (para usar varias veces más tarde)
function multiplicarDosNumeros(a, b) {
 return a * b;
}
const productos = numeros.reduce(multiplicarDosNumeros);
// .reduce funciona en cualquier tipo de datos.
// En este ejemplo configuramos un acumulador de arranque
const frases = palabras.reduce(function (acc, elemento) {
 return acc + " " + elemento;
}, "Frase completa:");
console.log(suma); // 45
console.log(productos); // 362880
console.log(frases); // "Frase completa: Hola, mi nombre es Martin"
.map
.map se usa cuando queremos cambiar cada elemento de una matriz de la misma manera. .map Toma una devolución de llamada como único argumento. Al igual que el método .forEach, el callback tiene el elemento y el índice de argumentos opcionales. A diferencia de .reduce, .map devolverá toda la matriz.
const numeros = [2, 3, 4, 5];
function multiplicarPorTres(elemento) {
 return elemento * 3;
}
const doble = numeros.map(function (elemento) {
 return elemento * 2;
});
const triple = numeros.map(multiplicarPorTres);
console.log(doble); // [ 4, 6, 8, 10 ]
console.log(triple); // [ 6, 9, 12, 15 ]
Comprensión de las funciones de devolución de llamada y cómo usarlas
En JavaScript, las funciones son objetos de primera clase; es decir, las funciones son del tipo Object y se pueden usar de primera clase como cualquier otro objeto (String, Array, Number, etc.) ya que en realidad son objetos en sí mismos. Pueden ser "almacenados en variables, pasados ​​como argumentos a funciones, creados dentro de funciones y devueltos desde funciones"  .
[sc:mongodb-libro]
Debido a que las funciones son objetos de primera clase, podemos pasar una función como argumento en otra función y luego ejecutar esa función pasada o incluso devolverla para que se ejecute más tarde. Esta es la esencia del uso de funciones de devolución de llamada en JavaScript. En el resto de este artículo, aprenderemos todo sobre las funciones de devolución de llamada de JavaScript. Las funciones de devolución de llamada son probablemente la técnica de programación funcional más utilizada en JavaScript, y puede encontrarlas en casi todas las piezas de código JavaScript y jQuery, sin embargo, siguen siendo un misterio para muchos desarrolladores de JavaScript. El misterio ya no existirá cuando termines de leer este artículo.
Las funciones de devolución de llamada se derivan de un paradigma de programación conocido como programación funcional . En un nivel fundamental, la programación funcional especifica el uso de funciones como argumentos. La programación funcional fue, y sigue siendo, aunque en mucha menor medida hoy en día, vista como una técnica esotérica de programadores maestros especialmente capacitados.
Afortunadamente, las técnicas de programación funcional han sido aclaradas para que simples mortales como tú y yo podamos entenderlas y usarlas con facilidad. Una de las técnicas principales en la programación funcional son las funciones de devolución de llamada . Como leerá en breve, implementar funciones de devolución de llamada es tan fácil como pasar variables regulares como argumentos. Esta técnica es tan simple que me pregunto por qué se cubre principalmente en temas avanzados de JavaScript.
¿Qué es una función de devolución de llamada o de orden superior?
Una función de devolución de llamada, también conocida como función de orden superior, es una función que se pasa a otra función (llamemos a esta otra función "otra función") como un parámetro, y la función de devolución de llamada se llama (o ejecuta) dentro de otra función. Una función de devolución de llamada es esencialmente un patrón (una solución establecida para un problema común) y, por lo tanto, el uso de una función de devolución de llamada también se conoce como patrón de devolución de llamada.
Considere este uso común de una función de devolución de llamada en jQuery:
//Note that the item in the click method's parameter is a function, not a variable.
//The item is a callback function
$("#btn_1").click(function() {
 alert("Btn 1 Clicked");
});
Como ves en el ejemplo anterior, pasamos una función como parámetro al método click . Y el método de clic llamará (o ejecutará) la función de devolución de llamada que le pasamos. Este ejemplo ilustra un uso típico de funciones de devolución de llamada en JavaScript y uno ampliamente utilizado en jQuery.
Reflexione sobre este otro ejemplo clásico de funciones de devolución de llamada en JavaScript básico:
var friends = ["Mike", "Stacy", "Andy", "Rick"];
friends.forEach(function (eachName, index){
console.log(index + 1 + ". " + eachName); // 1. Mike, 2. Stacy, 3. Andy, 
4. Rick
});
Una vez más, tenga en cuenta la forma en que pasamos una función anónima (una función sin nombre) al método forEach como parámetro.
Hasta ahora hemos pasado funciones anónimas como parámetro a otras funciones o métodos. Ahora comprendamos cómo funcionan las devoluciones de llamada antes de ver ejemplos más concretos y comenzar a crear nuestras propias funciones de devolución de llamada.
¿Cómofuncionan las funciones de devolución de llamada?
Podemos pasar funciones como variables y devolverlas en funciones y usarlas en otras funciones. Cuando pasamos una función de devolución de llamada como argumento a otra función, solo estamos pasando la definición de la función. No estamos ejecutando la función en el parámetro. En otras palabras, no estamos pasando la función con el par final de paréntesis de ejecución () como lo hacemos cuando estamos ejecutando una función.
Y dado que la función contenedora tiene la función de devolución de llamada en su parámetro como definición de función, puede ejecutar la devolución de llamada en cualquier momento.
Tenga en cuenta que la función de devolución de llamada no se ejecuta inmediatamente. Se "devuelve la llamada" (de ahí el nombre) en algún punto específico dentro del cuerpo de la función contenedora. Entonces, aunque el primer ejemplo de jQuery se veía así:
//The anonymous function is not being executed there in the parameter. 
//The item is a callback function
$("#btn_1").click(function() {
 alert("Btn 1 Clicked");
});
la función anónima se llamará más tarde dentro del cuerpo de la función. Incluso sin un nombre, aún se puede acceder a él más tarde a través del objeto de argumentos mediante la función contenedora.
Las funciones de devolución de llamada son cierres
Cuando pasamos una función de devolución de llamada como argumento a otra función, la devolución de llamada se ejecuta en algún punto dentro del cuerpo de la función contenedora como si la devolución de llamada estuviera definida en la función contenedora. Esto significa que la devolución de llamada es un cierre. Lea mi publicación, Comprender los cierres de JavaScript con facilidad para obtener más información sobre los cierres. Como sabemos, los cierres tienen acceso al alcance de la función contenedora, por lo que la función de devolución de llamada puede acceder a las variables de las funciones contenedoras, e incluso a las variables del alcance global.
Principios básicos al implementar funciones de devolución de llamada
Si bien no son complicadas, las funciones de devolución de llamada tienen algunos principios notables con los que debemos estar familiarizados al implementarlas.
Usar funciones con nombre O anónimas como devoluciones de llamada
En los ejemplos anteriores de jQuery y forEach, usamos funciones anónimas que se definieron en el parámetro de la función contenedora. Ese es uno de los patrones comunes para usar funciones de devolución de llamada. Otro patrón popular es declarar una función con nombre y pasar el nombre de esa función al parámetro. Considera esto:
// global variable
var allUserData = [];
// generic logStuff function that prints to console
function logStuff (userData) {
 if ( typeof userData === "string")
 {
 console.log(userData);
 }
 else if ( typeof userData === "object")
 {
 for (var item in userData) {
 console.log(item + ": " + userData[item]);
 }
 }
}
// A function that takes two parameters, the last one a callback function
function getInput (options, callback) {
 allUserData.push (options);
 callback (options);
}
// When we call the getInput function, we pass logStuff as a parameter.
// So logStuff will be the function that will called back (or executed) 
inside the getInput function
getInput ({name:"Rich", speciality:"JavaScript"}, logStuff);
// name: Rich
// speciality: JavaScript
Pasar parámetros a funciones de devolución de llamada
Dado que la función de devolución de llamada es solo una función normal cuando se ejecuta, podemos pasarle parámetros. Podemos pasar cualquiera de las propiedades de la función contenedora (o propiedades globales) como parámetros a la función de devolución de llamada. En el ejemplo anterior, pasamos opciones como parámetro a la función de devolución de llamada. Pasemos una variable global y una variable local:
//Global variable
var generalLastName = "Clinton";
function getInput (options, callback) {
 allUserData.push (options);
// Pass the global variable generalLastName to the callback function
 callback (generalLastName, options);
}
Asegúrese de que la devolución de llamada sea una función antes de ejecutarla
Siempre es aconsejable verificar que la función de devolución de llamada pasada en el parámetro sea realmente una función antes de llamarla. Además, es una buena práctica hacer que la función de devolución de llamada sea opcional.
Refactoricemos la función getInput del ejemplo anterior para garantizar que estas comprobaciones estén en su lugar.
function getInput(options, callback) {
 allUserData.push(options);
 // Make sure the callback is a function
 if (typeof callback === "function") {
 // Call it, since we have confirmed it is callable
 callback(options);
 }
}
Sin la verificación en su lugar, si se llama a la función getInput sin la función de devolución de llamada como parámetro o en lugar de una función se pasa una no función, nuestro código dará como resultado un error de tiempo de ejecución.
Problema al usar métodos con este objeto como devoluciones de llamada
Cuando la función de devolución de llamada es un método que usa este objeto , tenemos que modificar la forma en que ejecutamos la función de devolución de llamada para preservar el contexto de este objeto. O bien, este objeto apuntará al objeto de ventana global (en el navegador), si la devolución de llamada se pasó a una función global. O apuntará al objeto del método contenedor.
Exploremos esto en código:
// Define an object with some properties and a method
// We will later pass the method as a callback function to another function
var clientData = {
 id: 094545,
 fullName: "Not Set",
 // setUserName is a method on the clientData object
 setUserName: function (firstName, lastName) {
 // this refers to the fullName property in this object
 this.fullName = firstName + " " + lastName;
 }
}
function getUserInput(firstName, lastName, callback) {
 // Do other stuff to validate firstName/lastName here
 // Now save the names
 callback (firstName, lastName);
}
En el siguiente ejemplo de código, cuando se ejecuta clientData.setUserName, this.fullName no establecerá la propiedad fullName en el objeto clientData. En su lugar, establecerá fullName en el objeto de la ventana, ya que getUserInput es una función global. Esto sucede porque el objeto this en la función global apunta al objeto ventana.
getUserInput ("Barack", "Obama", clientData.setUserName);
console.log (clientData.fullName);// Not Set
// The fullName property was initialized on the window object
console.log (window.fullName); // Barack Obama
Use la función Llamar o Aplicar para preservar esto.
Podemos solucionar el problema anterior usando la función Llamar o Aplicar (discutiremos esto en una publicación de blog completa más adelante). Por ahora, sepa que cada función en JavaScript tiene dos métodos: Llamar y Aplicar. Y estos métodos se utilizan para establecer este objeto dentro de la función y para pasar argumentos a las funciones.
Call toma el valor que se usará como el objeto this dentro de la función como primer parámetro, y los argumentos restantes que se pasarán a la función se pasan individualmente (separados por comas, por supuesto). El primer parámetro de la función Aplicar también es el valor que se usará como objeto this dentro de la función, mientras que el último parámetro es una matriz de valores (o el objeto de argumentos ) para pasar a la función.
Esto suena complejo, pero veamos lo fácil que es usar Aplicar o Llamar. Para solucionar el problema del ejemplo anterior, utilizaremos la función Aplicar así:
//Note that we have added an extra parameter for the callback object,
 called "callbackObj"
function getUserInput(firstName, lastName, callback, callbackObj) {
 // Do other stuff to validate name here
 // The use of the Apply function below will set the this object to be 
callbackObjcallback.apply (callbackObj, [firstName, lastName]);
}
Con la función Aplicar configurando este objeto correctamente, ahora podemos ejecutar correctamente la devolución de llamada y hacer que establezca correctamente la propiedad fullName en el objeto clientData:
// We pass the clientData.setUserName method and the clientData object as 
parameters. The clientData object will be used by the Apply function to
 set the this object
getUserInput ("Barack", "Obama", clientData.setUserName, clientData);
// the fullName property on the clientData was correctly set
console.log (clientData.fullName); // Barack Obama
También habríamos usado la función Llamar , pero en este caso usamos la función Aplicar .
Múltiples funciones de devolución de llamada permitidas
Podemos pasar más de una función de devolución de llamada al parámetro de una función, al igual que podemos pasar más de una variable. Aquí hay un ejemplo clásico con la función AJAX de jQuery:
function successCallback() {
 // Do stuff before send
}
function successCallback() {
 // Do stuff if success message received
}
function completeCallback() {
 // Do stuff upon completion
}
function errorCallback() {
 // Do stuff if error received
}
$.ajax({
 url:"http://fiddle.jshell.net/favicon.png",
 success:successCallback,
 complete:completeCallback,
 error:errorCallback
});
Problema y solución del "infierno de devolución de llamada"
En la ejecución de código asíncrono, que es simplemente la ejecución de código en cualquier orden, a veces es común tener numerosos niveles de funciones de devolución de llamada en la medida en que tiene un código similar al siguiente. El código desordenado a continuación se llama infierno de devolución de llamada debido a la dificultad de seguir el código debido a las muchas devoluciones de llamada. Tomé este ejemplo de node-mongodb-native, un controlador MongoDB para Node.js. [2]. El siguiente código de ejemplo es solo para demostración :
var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 
27017, {}), {'pk':CustomPKFactory});
p_client.open(function(err, p_client) {
 p_client.dropDatabase(function(err, done) {
 p_client.createCollection('test_custom_key', function(err,
 collection) {
 collection.insert({'a':1}, function(err, docs) {
 collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, 
function(err, cursor) {
 cursor.toArray(function(err, items) {
 test.assertEquals(1, items.length);
 // Let's close the db
 p_client.close();
 });
 });
 });
 });
 });
});
No es probable que encuentre este problema a menudo en su código, pero cuando lo haga, y lo hará de vez en cuando, aquí hay dos soluciones para este problema. 
Asigne un nombre a sus funciones, declárelas y pase solo el nombre de la función como devolución de llamada, en lugar de definir una función anónima en el parámetro de la función principal.
Modularidad: separe su código en módulos, para que pueda exportar una sección de código que hace un trabajo en particular. Luego puede importar ese módulo a su aplicación más grande.
Haga sus propias funciones de devolución de llamada
Ahora que entiende completamente (creo que sí; si no, es una relectura rápida :)) todo lo relacionado con las funciones de devolución de llamada de JavaScript y ha visto que el uso de funciones de devolución de llamada es bastante simple pero poderoso, debe mirar su propio código en busca de oportunidades para use funciones de devolución de llamada, ya que le permitirán:
No repita el código (SECO: no se repita)
Implemente una mejor abstracción donde pueda tener funciones más genéricas que sean versátiles (puede manejar todo tipo de funcionalidades)
Tener mejor mantenibilidad
Tener un código más legible
Tienen funciones más especializadas.
Es bastante fácil hacer sus propias funciones de devolución de llamada. En el siguiente ejemplo, podría haber creado una función para hacer todo el trabajo: recuperar los datos del usuario, crear un poema genérico con los datos y saludar al usuario. Esta habría sido una función desordenada con muchas declaraciones if/else y, aún así, habría sido muy limitada e incapaz de llevar a cabo otras funcionalidades que la aplicación podría necesitar con los datos del usuario.
En su lugar, dejé la implementación de la funcionalidad adicional hasta las funciones de devolución de llamada, de modo que la función principal que recupera los datos del usuario puede realizar prácticamente cualquier tarea con los datos del usuario simplemente pasando el nombre completo y el género del usuario como parámetros a la función de devolución de llamada y luego ejecutando la función de devolución de llamada.
En resumen, la función getUserInput es versátil: puede ejecutar todo tipo de funciones de devolución de llamada con innumerables funcionalidades.
// First, setup the generic poem creator function; it will be the 
callback function in the getUserInput function below.
function genericPoemMaker(name, gender) {
 console.log(name + " is finer than fine wine.");
 console.log("Altruistic and noble for the modern time.");
 console.log("Always admirably adorned with the latest style.");
 console.log("A " + gender + " of unfortunate tragedies who still 
manages a perpetual smile");
}
//The callback, which is the last item in the parameter, will be our 
genericPoemMaker function we defined above.
function getUserInput(firstName, lastName, gender, callback) {
 var fullName = firstName + " " + lastName;
 // Make sure the callback is a function
 if (typeof callback === "function") {
 // Execute the callback function and pass the parameters to it
 callback(fullName, gender);
 }
}
Llame a la función getUserInput y pase la función genericPoemMaker como devolución de llamada:
getUserInput("Michael", "Fassbender", "Man", genericPoemMaker);
// Output
/* Michael Fassbender is finer than fine wine.
Altruistic and noble for the modern time.
Always admirably adorned with the latest style.
A Man of unfortunate tragedies who still manages a perpetual smile.
*/
Debido a que la función getUserInput solo maneja la recuperación de datos, podemos pasarle cualquier devolución de llamada. Por ejemplo, podemos pasar una función greetingUser como esta:
function greetUser(customerName, sex) {
 var salutation = sex && sex === "Man" ? "Mr." : "Ms.";
 console.log("Hello, " + salutation + " " + customerName);
}
// Pass the greetUser function as a callback to getUserInput
getUserInput("Bill", "Gates", "Man", greetUser);
// And this is the output
Hello, Mr. Bill Gates
Llamamos a la misma función getUserInput que hicimos antes, pero esta vez realizó una tarea completamente diferente.
Como puede ver, las funciones de devolución de llamada ofrecen mucha versatilidad. Y aunque el ejemplo anterior es relativamente simple, imagine cuánto trabajo puede ahorrarse y qué tan bien abstraído será su código si comienza a usar funciones de devolución de llamada. A por ello. Hazlo por las mañanas; hazlo por las tardes; hazlo cuando estés deprimido; hazlo cuando seas k
Tenga en cuenta las siguientes formas en que usamos con frecuencia funciones de devolución de llamada en JavaScript, especialmente en el desarrollo de aplicaciones web modernas, en bibliotecas y en marcos:
Para ejecución asincrónica (como leer archivos y realizar solicitudes HTTP)
Oyentes/controladores de eventos
En los métodos setTimeout y setInterval
Para generalización: concisión de código
Ultimas palabras
Las funciones de devolución de llamada de JavaScript son maravillosas y poderosas de usar y brindan grandes beneficios a sus aplicaciones web y código. Debe usarlos cuando surja la necesidad; busque formas de refactorizar su código para Abstracción, Mantenibilidad y Legibilidad con funciones de devolución de llamada.
Javascript elocuente: funciones de orden superiorUn programa grande es un programa costoso, y no solo por el tiempo que lleva construirlo. El tamaño casi siempre implica complejidad, y la complejidad confunde a los programadores. Los programadores confundidos, a su vez, introducen errores ( bugs ) en los programas. Luego, un programa grande proporciona mucho espacio para que estos errores se oculten, lo que los hace difíciles de encontrar.
Por ejemplo:
El primero es autónomo y tiene seis líneas.
 mar total = 0 , cuenta = 1 ;
while ( cuenta <= 10 ) {
 total += cuenta ;
 contar += 1 ;
}
consola _ registro ( total );
El segundo se basa en dos funciones externas y tiene una línea de largo.
consola _ registro ( suma ( rango ( 1 , 10 )));
¿Cuál es más probable que contenga un error?
Si contamos el tamaño de las definiciones de sumy range, el segundo programa también es grande, incluso más grande que el primero. Pero aún así, diría que es más probable que sea correcto.
Es más probable que sea correcta porque la solución se expresa en un vocabulario que corresponde al problema que se está resolviendo. Sumar un rango de números no se trata de bucles y contadores. Se trata de rangos y sumas.
Las definiciones de este vocabulario (las funciones sumy range) seguirán implicando bucles, contadores y otros detalles secundarios. Pero debido a que expresan conceptos más simples que el programa como un todo, es más fácil hacerlo bien.
Abstracción
En el contexto de la programación, este tipo de vocabularios suelen denominarse abstracciones . Las abstracciones ocultan detalles y nos dan la capacidad de hablar sobre problemas a un nivel superior (o más abstracto).
Como analogía, compare estas dos recetas de sopa de guisantes. El primero va así:
Ponga 1 taza de guisantes secos por persona en un recipiente. Agregue agua hasta que los guisantes estén bien cubiertos. Deja los guisantes en agua durante al menos 12 horas. Saca los guisantes del agua y colócalos en una cacerola. Agregue 4 tazas de agua por persona. Tape la cacerola y mantenga los guisantes hirviendo a fuego lento durante dos horas. Tomar media cebolla por persona. Córtalo en trozos con un cuchillo. Agrégalo a los guisantes. Tomar un tallo de apio por persona. Córtalo en trozos con un cuchillo. Agrégalo a los guisantes. Tomar una zanahoria por persona. Córtalo en pedazos. ¡Con un cuchillo! Agrégalo a los guisantes. Cocine por 10 minutos más.
Y esta es la segunda receta:
Por persona: 1 taza de guisantes secos, media cebolla picada, un tallo de apio y una zanahoria.
Remoje los guisantes durante 12 horas. Cocine a fuego lento durante 2 horas en 4 tazas de agua (por persona). Picar y agregar las verduras. Cocine por 10 minutos más.
El segundo es más corto y más fácil de interpretar. Pero sí necesita entender algunas palabras más relacionadas con la cocina, como remojar , hervir a fuego lento , picar y, supongo, vegetal .
Al programar, no podemos confiar en que todas las palabras que necesitamos nos estén esperando en el diccionario. Por lo tanto, podríamos caer en el patrón de la primera receta: resolver los pasos precisos que la computadora debe realizar, uno por uno, ciego a los conceptos de nivel superior que expresan.
Es una habilidad útil, en programación, darse cuenta cuando se está trabajando a un nivel de abstracción demasiado bajo.
abstracción de la repetición
Las funciones simples, como las hemos visto hasta ahora, son una buena forma de construir abstracciones. Pero a veces se quedan cortos.
Es común que un programa haga algo un número determinado de veces. Puedes escribir un forbucle para eso, así:
for ( sea i = 0 ; i < 10 ; i ++ ) {
 consola . registro ( yo );
}
¿Podemos abstraer “hacer algo N veces” como una función? Bueno, es fácil escribir una función que llame console.log N veces.
function repeatLog ( n ) {
 for ( let i = 0 ; i < n ; i ++ ) {
 console . registro ( yo );
 }
}
Pero, ¿y si queremos hacer algo más que registrar los números? Dado que "hacer algo" se puede representar como una función y las funciones son solo valores, podemos pasar nuestra acción como un valor de función.
función repetir ( n , acción ) {
 for ( let i = 0 ; i < n ; i ++ ) {
 acción ( i );
 }
}
repetir ( 3 , consola . log );
// → 0 
// → 1 
// → 2
No tenemos que pasar una función predefinida a repeat. A menudo, es más fácil crear un valor de función en el acto.
dejar etiquetas = [];
repetir ( 5 , i => {
 etiquetas . push ( `Unidad ${ i + 1 } ` );
});
consola _ registro ( etiquetas );
// → ["Unidad 1", "Unidad 2", "Unidad 3", "Unidad 4", "Unidad 5"]
Está estructurado un poco como un for bucle: primero describe el tipo de bucle y luego proporciona un cuerpo. Sin embargo, el cuerpo ahora se escribe como un valor de función, que está entre paréntesis de la llamada a repeat. Es por eso que tiene que cerrarse con la llave de cierre y el paréntesis de cierre. En casos como este ejemplo, donde el cuerpo es una sola expresión pequeña, también puede omitir las llaves y escribir el bucle en una sola línea.
Funciones de orden superior
Las funciones que operan sobre otras funciones, ya sea tomándolas como argumentos o devolviéndolas, se denominan funciones de orden superior . Como ya hemos visto que las funciones son valores regulares, no hay nada particularmente notable en el hecho de que tales funciones existan. El término proviene de las matemáticas, donde la distinción entre funciones y otros valores se toma más en serio.
Las funciones de orden superior nos permiten abstraer acciones , no solo valores. Vienen en varias formas. Por ejemplo, podemos tener funciones que crean nuevas funciones.
función mayor que ( n ) {
 return m => m > n ;
}
let mayor que 10 = mayor que ( 10 );
consola _ registro ( mayor que 10 ( 11 ));
// → verdadero
Y podemos tener funciones que cambien otras funciones.
función ruidosa ( f ) {
 retorno ( ... argumentos ) => {
 consola . log ( "llamando con" , args );
 let resultado = f ( ... argumentos );
 consola _ log ( "llamado con" , argumentos , ", devuelto" , resultado );
 resultado devuelto ;
 };
}
ruidoso ( Math . min )( 3 , 2 , 1 );
// → llamando con [3, 2, 1] 
// → llamó con [3, 2, 1] , devolvió 1
Incluso podemos escribir funciones que proporcionen nuevos tipos de flujo de control.
función a menos que ( prueba , luego ) {
 si ( ! prueba ) luego ();
}
repetir ( 3 , n => {
 a menos que ( n % 2 == 1 , () => {
 consola . log ( n , "es par" );
 });
});
// → 0 es par 
// → 2 es par
Hay un método de matriz incorporado, forEach que proporciona algo como un bucle for/ of como una función de orden superior.
[ "A" , "B" ]. forEach ( l => consola . log ( l ));
// → A 
// → B
Filtrado de matrices
Para encontrar los scripts en el conjunto de datos que todavía están en uso, la siguiente función puede ser útil. Filtra los elementos de una matriz que no pasan la prueba.
 filtro de función ( matriz , prueba ) {
 dejar pasar = [];
 para ( let elemento de la matriz ) {
 if ( prueba ( elemento )) {
 aprobado . empujar ( elemento );
 }
 }
 regreso pasado ;
}
consola _ registro ( filtro ( SCRIPTS , script => script . living ));
// → [{nombre: "Adlam",...},...]
La función usa el argumento llamado test, un valor de función, para llenar un "vacío" en el cálculo: el proceso de decidir qué elementos recolectar.
Observe cómo la filter función, en lugar de eliminar elementos de la matriz existente, crea una nueva matriz con solo los elementos que pasan la prueba. Esta función es pura . No modifica la matriz que se le da.
Like forEach, filter es un método de matriz estándar. El ejemplo definió la función solo para mostrar lo que hace internamente. De ahora en adelante, lo usaremos así:
consola _ log ( SCRIPTS . filtro ( s => s . dirección == "ttb" ));
// → [{nombre: "mongol",...},...]
Transformar con mapa
Digamos que tenemos una matriz de objetos que representan scripts, producidos al filtrar la SCRIPTS matriz de alguna manera. Pero queremos una matriz de nombres, que sea más fácil de inspeccionar.El map método transforma una matriz aplicando una función a todos sus elementos y creando una nueva matriz a partir de los valores devueltos. La nueva matriz tendrá la misma longitud que la matriz de entrada, pero la función habrá asignado su contenido a una nueva forma.
 mapa de funciones ( arreglo , transformar ) {
 let mapeado = [];
 for ( let elemento de la matriz ) {
 asignado . empujar ( transformar ( elemento ));
 }
 volver mapeado ;
}
let rtlScripts = SCRIPTS . filtro ( s => s . dirección == "rtl" );
consola _ registro ( mapa ( rtlScripts , s => s . nombre ));
// → ["Adlam", "Árabe", "Arameo imperial", …]
Me gusta forEachyfiltermap es un método de matriz estándar.
Resumiendo con reducir
Otra cosa común que se hace con las matrices es calcular un solo valor a partir de ellas.
La operación de orden superior que representa este patrón se llama reducir (a veces también llamada doblar ). Construye un valor tomando repetidamente un solo elemento de la matriz y combinándolo con el valor actual. Al sumar números, comenzaría con el número cero y, para cada elemento, lo agregaría a la suma.
Los parámetros a reduceson, además de la matriz, una función de combinación y un valor de inicio. Esta función es un poco menos sencilla que filterymap , así que échele un vistazo de cerca:
función reducir ( arreglo , combinar , iniciar ) {
 let actual = iniciar ;
 for ( let elemento de la matriz ) {
 actual = combinar ( actual , elemento );
 }
 corriente de retorno ;
}
consola _ log ( reducir ([ 1 , 2 , 3 , 4 ], ( a , b ) => a + b , 0 ));
// → 10
El método de matriz estándar reduce, que por supuesto corresponde a esta función, tiene una conveniencia adicional. Si su matriz contiene al menos un elemento, puede omitir el start argumento. El método tomará el primer elemento de la matriz como su valor inicial y comenzará a reducir en el segundo elemento.
consola _ log ([ 1 , 2 , 3 , 4 ]. reduce (( a , b ) => a + b ));
// → 10
Para usar reduce (dos veces) para encontrar el script con la mayor cantidad de caracteres, podemos escribir algo como esto:
function characterCount ( script ) {
 return script . rangos _ reducir (( contar , [ desde , hasta ]) => {
 volver contar + ( hasta - desde );
 }, 0 );
}
consola _ log ( SCRIPTS . reduce (( a , b ) => {
 return número de caracteres ( a ) < número de caracteres ( b ) ? b : a ;
}));
// → {nombre: "Han",...}
La characterCount función reduce los rangos asignados a un script sumando sus tamaños. Tenga en cuenta el uso de la desestructuración en la lista de parámetros de la función reductora. La segunda llamada a reduceluego usa esto para encontrar el script más grande comparando repetidamente dos scripts y devolviendo el más grande.
componibilidad
Considere cómo habríamos escrito el ejemplo anterior (encontrar el script más grande) sin funciones de orden superior. El código no es mucho peor.
let mayor = nulo ;
for ( let script de SCRIPTS ) {
 if ( mayor == nulo | | 
 characterCount ( mayor ) < characterCount ( script )) {
 mayor = script ;
 }
}
consola _ registro ( más grande );
// → {nombre: "Han",...}
Hay algunos enlaces más y el programa tiene cuatro líneas más. Pero sigue siendo muy legible.
Las funciones de orden superior comienzan a brillar cuando necesita componer operaciones. Como ejemplo, escribamos un código que encuentre el año de origen promedio para scripts vivos y muertos en el conjunto de datos.
función promedio ( matriz ) {
 retornar matriz . reducir (( a , b ) => a + b ) / array . longitud ;
}
consola _ log ( Math . round ( promedio (
 SCRIPTS . filter ( s => s . living ). map ( s => s . year ))));
// → 1165 
consola . log ( Math . round ( promedio (
 SCRIPTS . filter ( s => ! s . living ). map ( => s s . año ))));
// → 204
Esta no es una estadística terriblemente significativa o sorprendente. Pero espero que esté de acuerdo en que el código utilizado para calcularlo no es difícil de leer. Puede verlo como una canalización: comenzamos con todos los scripts, filtramos los vivos (o muertos), tomamos los años de esos, los promediamos y redondeamos el resultado.
Definitivamente, también podría escribir este cálculo como un gran bucle.
sea ​​total = 0 , cuenta = 0 ;
for ( let script de SCRIPTS ) {
 if ( script . living ) {
 total += script . año ;
 contar += 1 ;
 }
}
consola _ log ( Math . round ( total / count ));
// → 1165
Pero es más difícil ver qué se estaba calculando y cómo. Y debido a que los resultados intermedios no se representan como valores coherentes, sería mucho más trabajo extraer algo como averageen una función separada.
En términos de lo que la computadora realmente está haciendo, estos dos enfoques también son bastante diferentes. El primero generará nuevas matrices cuando se ejecute filtery map, mientras que el segundo calcula solo algunos números, haciendo menos trabajo. Por lo general, puede permitirse el enfoque legible, pero si está procesando matrices enormes y lo hace muchas veces, el estilo menos abstracto podría valer la velocidad adicional.
Cadenas y códigos de caracteres
Un uso del conjunto de datos sería averiguar qué script está usando un fragmento de texto. Veamos un programa que hace esto.
Recuerde que cada secuencia de comandos tiene una serie de rangos de códigos de caracteres asociados. Entonces, dado un código de carácter, podríamos usar una función como esta para encontrar el script correspondiente (si corresponde):
function characterScript ( código ) {
 for ( let script of SCRIPTS ) {
 if ( script . ranges . some (([ from , to ]) => {
 return code >= from & & code < to ;
 })) {
 devolver guión ;
 }
 }
 devolver nulo ;
}
consola _ registro ( script de caracteres ( 121 ));
// → {nombre: "latín",...}
El some método es otra función de orden superior. Toma una función de prueba y le dice si esa función devuelve verdadero para cualquiera de los elementos de la matriz.
Pero, ¿cómo obtenemos los códigos de caracteres en una cadena?
las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estas se denominan unidades de código . Inicialmente, se suponía que un código de caracteres Unicode encajaría dentro de dicha unidad (lo que le da un poco más de 65,000 caracteres). Cuando quedó claro que no sería suficiente, muchas personas se resistieron a la necesidad de usar más memoria por carácter. Para abordar estas preocupaciones, se inventó UTF-16, el formato utilizado por las cadenas de JavaScript. Describe la mayoría de los caracteres comunes usando una sola unidad de código de 16 bits, pero usa un par de dos de esas unidades para otras.
UTF-16 generalmente se considera una mala idea hoy en día. Parece casi intencionalmente diseñado para invitar a errores. Es fácil escribir programas que pretenden que las unidades de código y los caracteres son lo mismo. Y si su idioma no usa caracteres de dos unidades, parecerá que funciona bien. Pero tan pronto como alguien intenta usar dicho programa con algunos caracteres chinos menos comunes, se rompe. Afortunadamente, con la llegada de los emoji, todo el mundo ha comenzado a utilizar caracteres de dos unidades y la carga de lidiar con estos problemas se distribuye de manera más justa.
Desafortunadamente, las operaciones obvias en las cadenas de JavaScript, como obtener su longitud a través de la length propiedad y acceder a su contenido usando corchetes, solo se ocupan de las unidades de código.
// Dos personajes emoji, caballo y herradura 
let horseShoe = "🐴👟" ;
consola _ log ( herradura . longitud );
// → 4 
consola . registro ( herradura [ 0 ]);
// → (Medio carácter no válido) 
consola . registro ( herradura . charCodeAt ( 0 ));
// → 55357 (Código del medio carácter) 
consola . log ( herradura . codePointAt ( 0 ));
// → 128052 (Código real para emoji de caballo)
El método de JavaScript charCodeAt le proporciona una unidad de código, no un código de caracteres completo. El codePointAt método,agregado más tarde, proporciona un carácter Unicode completo. Entonces podríamos usar eso para obtener caracteres de una cadena. Pero el argumento pasado codePointAt sigue siendo un índice de la secuencia de unidades de código. Entonces, para ejecutar todos los caracteres en una cadena, aún tendríamos que lidiar con la cuestión de si un carácter ocupa una o dos unidades de código.
En el capítulo anterior , mencioné que un bucle for/ of también se puede usar en cadenas. Al igual que codePointAt, este tipo de bucle se introdujo en un momento en que las personas eran muy conscientes de los problemas con UTF-16. Cuando lo usa para recorrer una cadena, le da caracteres reales, no unidades de código.
let roseDragon = "🌹🐉" ;
for ( let char of roseDragon ) {
 consola . registro ( carácter );
}
// → 🌹 
// → 🐉
Si tiene un carácter (que será una cadena de una o dos unidades de código), puede usarlo codePointAt(0)para obtener su código.
Reconocer texto
Tenemos una characterScript función y una forma de recorrer correctamente los caracteres. El siguiente paso es contar los caracteres que pertenecen a cada guión. La siguiente abstracción de conteo será útil allí:
function contar por ( elementos , nombre del grupo ) {
 let cuenta = [];
 for ( let item of items ) {
 let name = groupName ( item );
 dar a conocer = cuenta . buscarÍndice ( c => c . nombre == nombre );
 if ( conocido == - 1 ) {
 cuenta . empujar ({ nombre, cuenta : 1 });
 } else {
 cuenta [ conocido ]. cuenta ++ ;
 }
 }
 el regreso cuenta ;
}
consola _ log ( cuenta por ([ 1 , 2 , 3 , 4 , 5 ], n => n > 2 ));
// → [{nombre: falso, cuenta: 2}, {nombre: verdadero, cuenta: 3}]
La countBy función espera una colección (cualquier cosa que podamos recorrer con for/ of) y una función que calcula un nombre de grupo para un elemento dado. Devuelve una matriz de objetos, cada uno de los cuales nombra un grupo y le indica la cantidad de elementos que se encontraron en ese grupo.
Utiliza otro método de matriz findIndex: . Este método es algo así como indexOf, pero en lugar de buscar un valor específico, encuentra el primer valor para el cual la función dada devuelve verdadero. Como indexOf, devuelve -1 cuando no se encuentra dicho elemento.
Usando countBy, podemos escribir la función que nos dice qué scripts se usan en un fragmento de texto.
función scripts de texto ( texto ) {
 let scripts = countBy ( text , char => {
 let script = script de caracteres ( char . codePointAt ( 0 ));
 return script ? script . nombre : "ninguno" ;
 }). filtro (({ nombre }) => nombre != "ninguno" );
 let total = scripts . reduce (( n , { cuenta }) => n + cuenta , 0 );
 si ( total == 0 ) devuelve "No se encontraron scripts" ;
 guiones de retorno . map (({ nombre , cuenta }) => {
 return `${ Math . ronda ( cuenta * 100 / total ) } % ${ nombre } ` ;
 }). unirse ( ", " );
}
consola _ registro ( guiones de texto ( '英国的狗说"guau", 俄罗斯的狗说"тяв"' ));
// → 61% Han, 22% Latín, 17% Cirílico
La función primero cuenta los caracteres por nombre, usando characterScript  para asignarles un nombre y recurriendo a la cadena "none"para los caracteres que no forman parte de ningún script. La filterllamada descarta la entrada for "none"de la matriz resultante ya que no estamos interesados ​​en esos caracteres.
Para poder calcular porcentajes, primero necesitamos el número total de caracteres que pertenecen a un script, que podemos calcular con reduce. Si no se encuentran dichos caracteres, la función devuelve una cadena específica. De lo contrario, transforma las entradas de conteo en cadenas legibles con map y luego las combina con join.
Resumen
Ser capaz de pasar valores de función a otras funciones es un aspecto profundamente útil de JavaScript. Nos permite escribir funciones que modelan cálculos con "brechas" en ellos. El código que llama a estas funciones puede llenar los espacios proporcionando valores de función.
Las matrices proporcionan una serie de métodos útiles de orden superior. Puede usar forEach para recorrer los elementos en una matriz. El filter método devuelve una nueva matriz que contiene solo los elementos que pasan la función de predicado. La transformación de una matriz al poner cada elemento a través de una función se realiza con map. Puede utilizar reduce para combinar todos los elementos de una matriz en un solo valor. El some método comprueba si algún elemento coincide con una función de predicado determinada. Y findIndex encuentra la posición del primer elemento que coincide con un predicado.

Continuar navegando