JavaScript funcional: ¿qué son las funciones de orden superior y por qué son necesarias?


"Funciones de un orden superior" es una de esas frases que a menudo se encuentran dispersas. Pero rara vez alguien puede detenerse y explicar de qué se trata. Es posible que ya sepa lo que se llaman funciones de orden superior. Pero, ¿cómo los usamos en proyectos reales? ¿Cuándo y por qué son útiles? ¿Podemos manipular el DOM con su ayuda? ¿O las personas que usan estas funciones simplemente se muestran? ¿Quizás sin sentido complican el código?

Solía ​​pensar que las funciones de orden superior son útiles. Ahora considero que son la propiedad más importante de JavaScript como lenguaje. Pero antes de discutir esto, primero descubramos qué son exactamente las funciones de orden superior. Y comenzaremos con funciones como variables.

Funciones como objetos de primera clase


En JavaScript, hay al menos tres formas (hay más en total) para escribir una nueva función. Primero, puede escribir una declaración de función :

// Take a DOM element and wrap it in a list item element. function itemise(el) { const li = document.createElement('li'); li.appendChild(el); return li; } 

Espero que lo entiendas todo. Además, probablemente sepa que puede escribir una expresión de función :

 const itemise = function(el) { const li = document.createElement('li'); li.appendChild(el); return li; } 

Y finalmente, hay otra forma de escribir la misma función, como una función de flecha :

 const itemise = (el) => { const li = document.createElement('li'); li.appendChild(el); return li; } 

En este caso, los tres métodos son equivalentes. Aunque esto no siempre sucede, en la práctica, cada método tiene pequeñas diferencias asociadas con lo que sucede con la magia de una palabra clave y etiquetas en particular en los seguimientos de pila.

Pero tenga en cuenta que los dos últimos ejemplos asignan la función a una variable. Parece una bagatela. ¿Por qué no asignar una función a una variable? Pero es muy importante. Las funciones en JavaScript pertenecen a la " primera clase ". Por lo tanto, podemos:

  • Asignar funciones a variables.
  • Pase funciones como argumentos a otras funciones.
  • Funciones de retorno de otras funciones.

Esto es maravilloso, pero ¿qué tiene que ver todo esto con las funciones de orden superior? Presta atención a los últimos dos puntos. Pronto volveremos a ellos, pero por ahora veamos algunos ejemplos.

Vimos la asignación de funciones a variables. ¿Qué hay de pasarlos como parámetros? Escribamos una función que se pueda usar con elementos DOM. Si ejecutamos document.querySelectorAll() , a cambio no obtenemos una matriz, sino una NodeList . NodeList no tiene un método .map() , como las matrices, por lo que escribimos esto:

 // Apply a given function to every item in a NodeList and return an array. function elListMap(transform, list) { // list might be a NodeList, which doesn't have .map(), so we convert // it to an array. return [...list].map(transform); } // Grab all the spans on the page with the class 'for-listing'. const mySpans = document.querySelectorAll('span.for-listing'); // Wrap each one inside an <li> element. We re-use the // itemise() function from earlier. const wrappedList = elListMap(itemise, mySpans); 

Aquí pasamos la función itemise como argumento a la función elListMap . Pero podemos usar elListMap no solo para crear listas. Por ejemplo, con su ayuda, puede agregar una clase a un conjunto de elementos:

 function addSpinnerClass(el) { el.classList.add('spinner'); return el; } // Find all the buttons with class 'loader' const loadButtons = document.querySelectorAll('button.loader'); // Add the spinner class to all the buttons we found. elListMap(addSpinnerClass, loadButtons); 

elLlistMap toma otra función como parámetro y convierte. Es decir, podemos usar elListMap para resolver diferentes problemas.

Observamos un ejemplo de pasar funciones como parámetros. Ahora hablemos de devolver una función de una función. ¿Cómo se ve?

Primero, escribimos la función antigua habitual. Necesitamos tomar una lista de elementos li y envolverlos en ul . Fácil:

 function wrapWithUl(children) { const ul = document.createElement('ul'); return [...children].reduce((listEl, child) => { listEl.appendChild(child); return listEl; }, ul); } 

¿Y si entonces tenemos un montón de elementos de párrafo que queremos envolver en un div ? No hay problema, escribiremos una función más para esto:

 function wrapWithDiv(children) { const div = document.createElement('div'); return [...children].reduce((divEl, child) => { divEl.appendChild(child); return divEl; }, div); } 

Funciona muy bien Sin embargo, estas dos funciones son muy similares, la única diferencia está en el elemento padre que creamos.

Ahora podríamos escribir una función que tome dos parámetros: el tipo del elemento primario y la lista de elementos secundarios. Pero hay otra opción. Podemos crear una función que devuelve una función. Por ejemplo:

 function createListWrapperFunction(elementType) { // Straight away, we return a function. return function wrap(children) { // Inside our wrap function, we can 'see' the elementType parameter. const parent = document.createElement(elementType); return [...children].reduce((parentEl, child) => { parentEl.appendChild(child); return parentEl; }, parent); } } 

Puede parecer un poco complicado al principio, así que dividamos el código. Creamos una función que simplemente devuelve otra función. Pero esta función de retorno recuerda el parámetro elementType . Y luego, cuando llamamos a la función devuelta, ya sabe qué elemento crear. Por lo tanto, puede crear wrapWithUl y wrapWithDiv :

 const wrapWithUl = createListWrapperFunction('ul'); // Our wrapWithUl() function now 'remembers' that it creates a ul element. const wrapWithDiv = createListWreapperFunction('div'); // Our wrapWithDiv() function now 'remembers' that it creates a div element. 

Este truco, cuando la función devuelta "recuerda" algo, se llama cierre . Puedes leer más sobre ellos aquí . Los cierres son increíblemente convenientes, pero por ahora, no pensaremos en ellos.

Entonces, resolvimos:

  • Asignación de una función a una variable.
  • Pasando una función como parámetro.
  • Devolver una función de otra función ...

En general, las funciones de la primera clase son algo agradable. Pero, ¿qué tiene que ver la función de orden superior con ella? Veamos la definición.

¿Qué es una función de orden superior?


Definición : esta es una función que toma una función como argumento o devuelve una función como resultado.

¿Eso es familiar? En JavaScript, estas son funciones de primera clase. Es decir, las "funciones de orden superior" tienen exactamente las mismas ventajas. En otras palabras, es solo un nombre fantasioso para una idea simple.

Ejemplos de funciones de orden superior


Si comienzas a buscar, entonces comienzas a notar funciones de orden superior en todas partes. Las más comunes son las funciones que toman otras funciones como parámetros.

Funciones que toman otras funciones como parámetros.


Cuando pasa una devolución de llamada, utiliza una función de orden superior. En el desarrollo front-end, se encuentran en todas partes. Uno de los más comunes es el método .addEventListener() . Lo usamos cuando queremos realizar acciones en respuesta a algunos eventos. Por ejemplo, quiero hacer un botón que muestre una advertencia:

 function showAlert() { alert('Fallacies do not cease to be fallacies because they become fashions'); } document.body.innerHTML += `<button type="button" class="js-alertbtn"> Show alert </button>`; const btn = document.querySelector('.js-alertbtn'); btn.addEventListener('click', showAlert); 

Aquí creamos una función que muestra una advertencia, agregamos un botón a la página y pasamos la función showAlert() como argumento a btn.addEventListener() .

También encontramos funciones de orden superior cuando usamos métodos de iteración de matriz : por ejemplo, .map() , .filter() y .reduce() . Como en la función elListMap() :

 function elListMap(transform, list) { return [...list].map(transform); } 

Las funciones de orden superior también ayudan a trabajar con retrasos y tiempos. Las setTimeout() y setInterval() ayudan a controlar cuándo se ejecutan las funciones. Por ejemplo, si necesita eliminar la clase de highlight después de 30 segundos, puede hacer esto de la siguiente manera:

 function removeHighlights() { const highlightedElements = document.querySelectorAll('.highlighted'); elListMap(el => el.classList.remove('highlighted'), highlightedElements); } setTimeout(removeHighlights, 30000); 

Nuevamente, creamos una función y la pasamos a otra función como argumento.

Como puede ver, JavaScript a menudo tiene funciones que aceptan otras funciones. Y probablemente ya los uses.

Funciones Funciones de retorno


Las funciones de este tipo no se encuentran con tanta frecuencia como las anteriores. Pero también son útiles. Uno de los mejores ejemplos es la función maybe () . Adapte una variante del libro Allongé JavaScript :

 function maybe(fn) return function _maybe(...args) { // Note that the == is deliberate. if ((args.length === 0) || args.some(a => (a == null)) { return undefined; } return fn.apply(this, args); } } 

En lugar de comprender el código, primero veamos cómo se puede aplicar. Veamos elListMap() función elListMap() :

 // Apply a given function to every item in a NodeList and return an array. function elListMap(transform, list) { // list might be a NodeList, which doesn't have .map(), so we convert // it to an array. return [...list].map(transform); } 

¿Qué sucede si accidentalmente paso un valor nulo o indefinido a elListMap() ? Obtendremos un TypeError y una caída de la operación actual, sea lo que sea. Esto se puede evitar con la función maybe() :

 const safeElListMap = maybe(elListMap); safeElListMap(x => x, null); // ← undefined 

En lugar de caer, la función volverá undefined . Y si pasáramos esto a otra función protegida por maybe() , volveríamos a estar undefined . maybe() pueda proteger cualquier número de funciones, es mucho más fácil escribir mil millones de if .

Las funciones que devuelven funciones también son comunes en el mundo React. Por ejemplo, connect() .

Entonces, ¿qué sigue?


Vimos varios ejemplos de uso de funciones de orden superior. Entonces, ¿qué sigue? ¿Qué nos pueden dar lo que no podemos obtener sin ellos?

Para responder a esta pregunta, veamos otro ejemplo: el método de matriz .sort() . Sí, tiene fallas. Cambia la matriz en lugar de devolver una nueva. Pero olvidémoslo por ahora. El método .sort() es una función de orden superior; toma otra función como uno de los parámetros.

Como funciona Si queremos ordenar una matriz de números, primero debemos crear una función de comparación:

 function compareNumbers(a, b) { if (a === b) return 0; if (a > b) return 1; /* else */ return -1; } 

Ahora ordena la matriz:

 let nums = [7, 3, 1, 5, 8, 9, 6, 4, 2]; nums.sort(compareNumbers); console.log(nums); // 〕[1, 2, 3, 4, 5, 6, 7, 8, 9] 

Puedes ordenar listas de números. ¿Pero de qué sirve? ¿Con qué frecuencia tenemos una lista de números para ordenar? No a menudo Por lo general, necesito ordenar una variedad de objetos:

 let typeaheadMatches = [ { keyword: 'bogey', weight: 0.25, matchedChars: ['bog'], }, { keyword: 'bog', weight: 0.5, matchedChars: ['bog'], }, { keyword: 'boggle', weight: 0.3, matchedChars: ['bog'], }, { keyword: 'bogey', weight: 0.25, matchedChars: ['bog'], }, { keyword: 'toboggan', weight: 0.15, matchedChars: ['bog'], }, { keyword: 'bag', weight: 0.1, matchedChars: ['b', 'g'], } ]; 

Supongamos que quiero ordenar esta matriz por el peso de cada registro. Podría escribir una nueva función de clasificación desde cero. Pero por qué, si puedes crear una nueva función de comparación:

 function compareTypeaheadResult(word1, word2) { return -1 * compareNumbers(word1.weight, word2.weight); } typeaheadMatches.sort(compareTypeaheadResult); console.log(typeaheadMatches); // 〕[{keyword: "bog", weight: 0.5, matchedChars: ["bog"]}, … ] 

Puede escribir una función de comparación para cualquier tipo de matriz. El método .sort() nos ayuda: “Si me das una función de comparación, .sort() cualquier matriz. No te preocupes por su contenido. Si le das una función de clasificación, la ordenaré ”. Por lo tanto, no necesitamos escribir un algoritmo de clasificación por nuestra cuenta, nos centraremos en la tarea mucho más simple de comparar dos elementos.

Ahora imagine que no estamos utilizando funciones de orden superior. No podemos pasar una función al método .sort() . Tendremos que escribir una nueva función de clasificación cada vez que necesitemos ordenar una matriz de un tipo diferente. O tiene que reinventar lo mismo con punteros de función u objetos. En cualquier caso, resultará muy incómodo.

Sin embargo, tenemos funciones de orden superior que nos permiten separar la función de clasificación de la función de comparación. Digamos que un desarrollador de navegador inteligente ha actualizado .sort() para usar un algoritmo más rápido. Entonces su código solo ganará, independientemente de lo que esté dentro de las matrices ordenables. Y este esquema es cierto para un conjunto completo de funciones de matrices de orden superior .

Esto nos lleva a tal idea. El método .sort() abstrae la tarea de clasificación del contenido de la matriz. Esto se llama separación de preocupaciones. Las funciones de orden superior le permiten crear abstracciones que sin ellas serían muy engorrosas o incluso imposibles. Y la creación de abstracciones representa el 80% del trabajo de los ingenieros de software.

Cuando refactorizamos el código para eliminar repeticiones, creamos abstracciones. Vemos el patrón y lo reemplazamos con una representación abstracta. Como resultado, el código se vuelve más significativo y más fácil de entender. Al menos ese es el objetivo.

Las funciones de orden superior son una herramienta poderosa para crear abstracciones. Y con las abstracciones, se asocia toda una rama de las matemáticas, la teoría de categorías. Más precisamente, la teoría de categorías se dedica a la búsqueda de abstracciones de abstracciones. En otras palabras, estamos hablando de encontrar patrones de patrones. Y en los últimos 70 años, los programadores inteligentes tomaron prestadas muchas ideas de allí, que se convirtieron en propiedades de idiomas y bibliotecas. Si aprendemos estos patrones, a veces podemos reemplazar grandes piezas de código. O simplifique problemas complejos a combinaciones elegantes de bloques de construcción simples. Estos bloques son funciones de orden superior. Por lo tanto, son tan importantes que nos brindan una herramienta poderosa para combatir la complejidad de nuestro código.

Materiales adicionales sobre funciones de orden superior:


Probablemente ya esté utilizando funciones de orden superior. Esto es tan fácil en JavaScript que ni siquiera pensamos en ello. Pero es mejor saber de qué están hablando las personas cuando dicen esta frase. Esto no es dificil. Pero detrás de una idea simple hay mucho poder.

Si tiene experiencia en programación funcional, puede notar que no utilicé funciones puras y algunos ... nombres detallados de funciones. Esto no se debe a que no haya escuchado sobre las funciones inmundas o los principios generales de la programación funcional. Y no escribo ese código en producción. Traté de recoger ejemplos prácticos que serían claros para los principiantes. A veces tenía que comprometerme. Si está interesado, ya escribí sobre la limpieza funcional y los principios generales de la programación funcional .

Source: https://habr.com/ru/post/458896/


All Articles