Como ya sabemos, una función en JavaScript es un valor.
Cada valor en JavaScript tiene un tipo. ¿Qué tipo es una función?
En JavaScript, las funciones son objetos.
Una buena forma de imaginar funciones es como "objetos de acción" invocables. No sólo podemos llamarlos, sino también tratarlos como objetos: agregar/eliminar propiedades, pasar por referencia, etc.
Los objetos de función contienen algunas propiedades utilizables.
Por ejemplo, se puede acceder al nombre de una función como la propiedad "nombre":
función decir Hola() { alerta("Hola"); } alerta(diHola.nombre); // decir hola
Lo que es curioso, la lógica de asignación de nombres es inteligente. También asigna el nombre correcto a una función incluso si se crea sin uno y luego se asigna inmediatamente:
digamosHola = función() { alerta("Hola"); }; alerta(diHola.nombre); // saluda (¡hay un nombre!)
También funciona si la asignación se realiza mediante un valor predeterminado:
función f(decirHola = función() {}) { alerta(diHola.nombre); // saluda (¡funciona!) } F();
En la especificación, esta característica se denomina "nombre contextual". Si la función no proporciona ninguno, entonces en una tarea se deduce del contexto.
Los métodos de objetos también tienen nombres:
dejar usuario = { decir Hola() { //... }, Di adiós: función() { //... } } alerta(usuario.decirHola.nombre); // decir hola alerta(usuario.decirAdiós.nombre); // decir adios
Aunque no hay magia. Hay casos en los que no hay forma de encontrar el nombre correcto. En ese caso, la propiedad de nombre está vacía, como aquí:
// función creada dentro de la matriz let arr = [función() {}]; alerta(arr[0].nombre); // <cadena vacía> // el motor no tiene forma de configurar el nombre correcto, por lo que no hay ninguno
En la práctica, sin embargo, la mayoría de las funciones tienen un nombre.
Hay otra propiedad incorporada "longitud" que devuelve el número de parámetros de la función, por ejemplo:
función f1(a) {} función f2(a,b) {} función muchos(a, b, ...más) {} alerta(f1.longitud); // 1 alerta(f2.longitud); // 2 alerta(muchas.longitudes); // 2
Aquí podemos ver que los parámetros de descanso no se cuentan.
La propiedad length
se utiliza a veces para la introspección en funciones que operan sobre otras funciones.
Por ejemplo, en el código siguiente, la función ask
acepta una question
para hacer y un número arbitrario de funciones handler
para llamar.
Una vez que un usuario proporciona su respuesta, la función llama a los controladores. Podemos pasar dos tipos de controladores:
Una función sin argumentos, que solo se llama cuando el usuario da una respuesta positiva.
Una función con argumentos, que se llama en cualquier caso y devuelve una respuesta.
Para llamar handler
de la manera correcta, examinamos la propiedad handler.length
.
La idea es que tengamos una sintaxis de controlador simple y sin argumentos para casos positivos (variante más frecuente), pero que también podamos admitir controladores universales:
función preguntar (pregunta, ... controladores) { let isYes = confirmar(pregunta); for(let handler of handlers) { si (handler.length == 0) { si (esSí) controlador(); } demás { controlador(esSí); } } } // para una respuesta positiva, se llama a ambos controladores // para respuesta negativa, solo la segunda preguntar("¿Pregunta?", () => alerta('Dijiste que sí'), resultado => alerta(resultado));
Este es un caso particular del llamado polimorfismo: tratar los argumentos de manera diferente según su tipo o, en nuestro caso, según su length
. La idea tiene un uso en las bibliotecas de JavaScript.
También podemos agregar propiedades propias.
Aquí agregamos la propiedad counter
para rastrear el recuento total de llamadas:
función decir Hola() { alerta("Hola"); //contemos cuantas veces corremos decir Hola.counter++; } decir Hola.contador = 0; // valor inicial decir Hola(); // Hola decir Hola(); // Hola alert(`Llamado ${sayHi.counter} veces`); // Llamado 2 veces
Una propiedad no es una variable.
Una propiedad asignada a una función como sayHi.counter = 0
no define un counter
de variable local dentro de ella. En otras palabras, un counter
de propiedad y un let counter
de variable son dos cosas no relacionadas.
Podemos tratar una función como un objeto, almacenar propiedades en ella, pero eso no tiene ningún efecto en su ejecución. Las variables no son propiedades de funciones y viceversa. Estos son sólo mundos paralelos.
Las propiedades de función a veces pueden reemplazar los cierres. Por ejemplo, podemos reescribir el ejemplo de la función de contador del capítulo Alcance variable, cierre para usar una propiedad de función:
función crearContador() { // en lugar de: // deja contar = 0 contador de funciones() { devolver contador.count++; }; contador.count = 0; contador de devoluciones; } let contador = makeCounter(); alerta( contador() ); // 0 alerta( contador() ); // 1
El count
ahora se almacena directamente en la función, no en su entorno léxico externo.
¿Es mejor o peor que usar un cierre?
La principal diferencia es que si el valor de count
reside en una variable externa, entonces el código externo no puede acceder a ella. Sólo las funciones anidadas pueden modificarlo. Y si está vinculado a una función, entonces tal cosa es posible:
función crearContador() { contador de funciones() { devolver contador.count++; }; contador.count = 0; contador de devoluciones; } let contador = makeCounter(); contador.count = 10; alerta( contador() ); // 10
Por tanto, la elección de la implementación depende de nuestros objetivos.
Expresión de función nombrada, o NFE, es un término para expresiones de función que tienen un nombre.
Por ejemplo, tomemos una expresión de función ordinaria:
digamosHola = función(quién) { alerta(`Hola, ${quién}`); };
Y agrégale un nombre:
digamosHola = función func(quién) { alerta(`Hola, ${quién}`); };
¿Conseguimos algo aquí? ¿Cuál es el propósito de ese nombre "func"
adicional?
Primero, observemos que todavía tenemos una expresión de función. Agregar el nombre "func"
después de function
no la convirtió en una Declaración de función, porque todavía se crea como parte de una expresión de asignación.
Agregar ese nombre tampoco rompió nada.
La función todavía está disponible como sayHi()
:
digamosHola = función func(quién) { alerta(`Hola, ${quién}`); }; decir Hola("Juan"); // Hola, Juan
Hay dos cosas especiales sobre el nombre func
, que son las razones de ello:
Permite que la función haga referencia a sí misma internamente.
No es visible fuera de la función.
Por ejemplo, la función sayHi
a continuación se llama a sí misma nuevamente con "Guest"
si no se proporciona who
:
digamosHola = función func(quién) { si (quién) { alerta(`Hola, ${quién}`); } demás { func("Invitado"); // usa func para volver a llamarse a sí mismo } }; decir Hola(); // Hola, invitado // Pero esto no funcionará: función(); // Error, la función no está definida (no es visible fuera de la función)
¿Por qué usamos func
? ¿Quizás simplemente usar sayHi
para la llamada anidada?
De hecho, en la mayoría de los casos podemos:
digamosHola = función(quién) { si (quién) { alerta(`Hola, ${quién}`); } demás { decir Hola("Invitado"); } };
El problema con ese código es que sayHi
puede cambiar en el código externo. Si la función se asigna a otra variable, el código comenzará a dar errores:
digamosHola = función(quién) { si (quién) { alerta(`Hola, ${quién}`); } demás { decir Hola("Invitado"); // Error: decir Hola no es una función } }; let bienvenido = decir hola; decir Hola = nulo; bienvenido(); // Error, ¡la llamada anidada sayHi ya no funciona!
Esto sucede porque la función toma sayHi
de su entorno léxico externo. No hay sayHi
local, por lo que se utiliza la variable externa. Y en el momento de la llamada ese sayHi
externo es null
.
El nombre opcional que podemos poner en la Expresión de Función está destinado a resolver exactamente este tipo de problemas.
Usémoslo para arreglar nuestro código:
digamosHola = función func(quién) { si (quién) { alerta(`Hola, ${quién}`); } demás { func("Invitado"); // Ahora todo bien } }; let bienvenido = decir hola; decir Hola = nulo; bienvenido(); // Hola, invitado (la llamada anidada funciona)
Ahora funciona, porque el nombre "func"
es función local. No está tomado desde fuera (y no es visible allí). La especificación garantiza que siempre hará referencia a la función actual.
El código externo todavía tiene su variable sayHi
o welcome
. Y func
es un "nombre de función interna", la forma en que la función puede llamarse a sí misma de manera confiable.
No existe tal cosa para la declaración de función
La función de "nombre interno" que se describe aquí solo está disponible para expresiones de función, no para declaraciones de función. Para las declaraciones de funciones, no existe una sintaxis para agregar un nombre "interno".
A veces, cuando necesitamos un nombre interno confiable, es el motivo para reescribir una declaración de función en el formulario de expresión de función nombrada.
Las funciones son objetos.
Aquí cubrimos sus propiedades:
name
: el nombre de la función. Generalmente se toma de la definición de la función, pero si no hay ninguna, JavaScript intenta adivinarla a partir del contexto (por ejemplo, una asignación).
length
: el número de argumentos en la definición de la función. Los parámetros de descanso no se cuentan.
Si la función se declara como una expresión de función (no en el flujo de código principal) y lleva el nombre, entonces se denomina expresión de función nombrada. El nombre se puede usar internamente para hacer referencia a sí mismo, para llamadas recursivas o algo así.
Además, las funciones pueden tener propiedades adicionales. Muchas bibliotecas de JavaScript conocidas hacen un gran uso de esta función.
Crean una función "principal" y le adjuntan muchas otras funciones "auxiliares". Por ejemplo, la biblioteca jQuery crea una función llamada $
. La biblioteca lodash crea una función _
y luego le agrega _.clone
, _.keyBy
y otras propiedades (consulte los documentos cuando desee obtener más información sobre ellos). En realidad, lo hacen para disminuir la contaminación del espacio global, de modo que una sola biblioteca proporcione sólo una variable global. Eso reduce la posibilidad de conflictos de nombres.
Por lo tanto, una función puede hacer un trabajo útil por sí misma y también incluir muchas otras funciones en las propiedades.
importancia: 5
Modifique el código de makeCounter()
para que el contador también pueda disminuir y establecer el número:
counter()
debería devolver el siguiente número (como antes).
counter.set(value)
debe establecer el contador en value
.
counter.decrease()
debería disminuir el contador en 1.
Consulte el código de la zona de pruebas para ver el ejemplo de uso completo.
PD: Puede utilizar un cierre o la propiedad de función para mantener el recuento actual. O escribir ambas variantes.
Abra una caja de arena con pruebas.
La solución utiliza count
en la variable local, pero los métodos de suma se escriben directamente en el counter
. Comparten el mismo entorno léxico externo y también pueden acceder al count
actual.
función crearContador() { deja contar = 0; contador de funciones() { recuento de retorno ++; } contador.set = valor => recuento = valor; contador.disminución = () => contar--; contador de devoluciones; }
Abra la solución con pruebas en un sandbox.
importancia: 2
Escriba la función sum
que funcionaría así:
suma(1)(2) == 3; // 1 + 2 suma(1)(2)(3) == 6; // 1 + 2 + 3 suma(5)(-1)(2) == 6 suma(6)(-1)(-2)(-3) == 0 suma(0)(1)(2)(3)(4)(5) == 15
Sugerencia de PD: es posible que necesite configurar un objeto personalizado para una conversión primitiva para su función.
Abra una caja de arena con pruebas.
Para que todo funcione de todos modos , el resultado de sum
debe ser una función.
Esa función debe mantener en memoria el valor actual entre llamadas.
Según la tarea, la función debe convertirse en el número cuando se usa en ==
. Las funciones son objetos, por lo que la conversión ocurre como se describe en el capítulo Conversión de objeto a primitiva, y podemos proporcionar nuestro propio método que devuelva el número.
Ahora el código:
función suma(a) { let SumaActual = a; función f(b) { sumaactual += b; devolver f; } f.toString = función() { devolver sumaactual; }; devolver f; } alerta( suma(1)(2) ); // 3 alerta( suma(5)(-1)(2) ); // 6 alerta( suma(6)(-1)(-2)(-3) ); // 0 alerta( suma(0)(1)(2)(3)(4)(5) ); // 15
Tenga en cuenta que la función sum
en realidad funciona solo una vez. Devuelve la función f
.
Luego, en cada llamada posterior, f
agrega su parámetro a la suma currentSum
y regresa a sí mismo.
No hay recursividad en la última línea de f
.
Así es como se ve la recursividad:
función f(b) { sumaactual += b; devolver f(); // <-- llamada recursiva }
Y en nuestro caso, simplemente devolvemos la función, sin llamarla:
función f(b) { sumaactual += b; devolver f; // <-- no se llama a sí mismo, se devuelve a sí mismo }
Esta f
se utilizará en la próxima llamada y volverá a aparecer tantas veces como sea necesario. Luego, cuando se usa como un número o una cadena, toString
devuelve currentSum
. También podríamos usar Symbol.toPrimitive
o valueOf
aquí para la conversión.
Abra la solución con pruebas en un sandbox.