"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 :
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:
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; }
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) {
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');
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) {
En lugar de comprender el código, primero veamos cómo se puede aplicar. Veamos
elListMap()
función
elListMap()
:
¿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);
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; return -1; }
Ahora ordena la matriz:
let nums = [7, 3, 1, 5, 8, 9, 6, 4, 2]; nums.sort(compareNumbers); console.log(nums);
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);
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 .