Como ya sabemos, una funcin en JavaScript es un valor.
Cada valor en JavaScript tiene un tipo. Qu tipo es una funcin?
En JavaScript, las funciones son objetos.
Una buena manera de imaginar funciones es como objetos de accin invocables. No solo podemos llamarlos, sino tambin tratarlos como objetos: agregar/eliminar propiedades, pasar por referencia, etc.
La propiedad name
Las funciones como objeto contienen algunas propiedades utilizables.
Por ejemplo, el nombre de una funcin es accesible mediante la propiedad name:
function sayHi() {
alert("Hi");
}
alert(sayHi.name); // sayHi
Lo que es divertido, es que la lgica de asignacin de nombres es inteligente. Tambin da el nombre correcto a una funcin, incluso si se cre sin uno:
let sayHi = function() {
alert("Hi");
};
alert(sayHi.name); // sayHi (hay un nombre!)
Tambin funciona si la asignacin se realiza mediante un valor predeterminado:
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (funciona!)
}
f();
En la especificacin, esta caracterstica se denomina nombre contextual. Si la funcin no proporciona una, entonces en una asignacin se deduce del contexto.
Los mtodos de objeto tambin tienen nombres:
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
Sin embargo, no hay magia. Hay casos en que no hay forma de encontrar el nombre correcto. En ese caso, la propiedad name est vaca, como aqu:
// funcin creada dentro de un array
let arr = [function() {}];
alert( arr[0].name ); // <empty string>
// el motor no tiene forma de establecer el nombre correcto, por lo que no asigna ninguno
En la prctica, sin embargo, la mayora de las funciones tienen un nombre.
La propiedad length
Hay una nueva propiedad length incorporada que devuelve el nmero de parmetros de una funcin, por ejemplo:
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2
Aqu podemos ver que los parmetros rest no se cuentan.
La propiedad length a veces se usa para introspeccin en funciones que operan en otras funciones.
Por ejemplo, en el siguiente cdigo, la funcin ask , acepta una question y un nmero arbitrario de funciones controladoras o handler para llamar.
Una vez que un usuario proporciona su respuesta, la funcin llama a los controladores. Podemos pasar dos tipos de controladores:
- Una funcin de cero argumentos, que solo se llama cuando el usuario da una respuesta positiva.
- Una funcin con argumentos, que se llama en cualquier caso y devuelve una respuesta.
Para llamar a handler de la manera correcta, examinamos la propiedad handler.length.
La idea es que tenemos una sintaxis de controlador simple y sin argumentos para casos positivos (la variante ms frecuente), pero tambin podemos admitir controladores universales:
function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// para una respuesta positiva, se llaman ambos controladores
// para respuesta negativa, solo el segundo
ask("Question?", () => alert('You said yes'), result => alert(result));
Este es un caso particular llamado polimorfismo tratar los argumentos de manera diferente segn su tipo o, en nuestro caso, segn la longitud. La idea tiene un uso en las bibliotecas de JavaScript.
Propiedades personalizadas
Tambin podemos agregar nuestras propias propiedades.
Aqu agregamos la propiedad counter para registrar el recuento total de llamadas:
function sayHi() {
alert("Hi");
//vamos a contar las veces que se ejecuta
sayHi.counter++;
}
sayHi.counter = 0; // valor inicial
sayHi(); // Hi
sayHi(); // Hi
alert( `Called ${sayHi.counter} times` ); // Llamamos 2 veces
Una propiedad asignada a una funcin como sayHi.counter = 0 no define una variable local counter dentro de ella. En otras palabras, una propiedad counter y una variable let counter son dos cosas no relacionadas.
Podemos tratar una funcin como un objeto, almacenar propiedades en ella, pero eso no tiene ningn efecto en su ejecucin. Las variables no son propiedades de la funcin y viceversa. Estos solo son dos mundos paralelos.
Las propiedades de la funcin a veces pueden reemplazar las clausuras o closures. Por ejemplo, podemos reescribir el ejemplo de la funcin de contador del captulo mbito de Variable y el concepto "closure" para usar una propiedad de funcin:
function makeCounter() {
// en vez de:
// let count = 0
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
count ahora se almacena en la funcin directamente, no en su entorno lxico externo.
Es mejor o peor que usar una clausura (closure)?
La principal diferencia es que si el valor de count vive en una variable externa, entonces el cdigo externo no puede acceder a l. Solo las funciones anidadas pueden modificarlo. Y si est vinculado a una funcin, entonces tal cosa es posible:
function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
counter.count = 10;
alert( counter() ); // 10
Por lo tanto, la eleccin de la implementacin depende de nuestros objetivos.
Expresin de Funcin con Nombre
Named Function Expression, o NFE, es un trmino para Expresiones de funciones que tienen un nombre.
Por ejemplo, tomemos una expresin de funcin ordinaria:
let sayHi = function(who) {
alert(`Hello, ${who}`);
};
Y agrgale un nombre:
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
Logramos algo aqu? Cul es el propsito de ese nombre adicional de "func"?
Primero, tengamos en cuenta que todava tenemos una Expresin de Funcin. Agregar el nombre "func" despus de function no lo convirti en una Declaracin de Funcin, porque todava se crea como parte de una expresin de asignacin.
Agregar ese nombre tampoco rompi nada.
La funcin todava est disponible como sayHi():
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
sayHi("John"); // Hello, John
Hay dos cosas especiales sobre el nombre func, que le hacen til:
- Permite que la funcin se haga referencia internamente.
- No es visible fuera de la funcin
Por ejemplo, la funcin sayHi a continuacin se vuelve a llamar a s misma con "Guest" si no se proporciona who:
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // usa func para volver a llamarse a s misma
}
};
sayHi(); // Hello, Guest
// Pero esto no funcionar.
func(); // Error, func no est definido (no visible fuera de la funcin)
Por qu usamos func? Quizs solo usa sayHi para la llamada anidada?
En realidad, en la mayora de los casos podemos:
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest");
}
};
El problema con ese cdigo es que sayHi puede cambiar en el cdigo externo. Si la funcin se asigna a otra variable, el cdigo comenzar a dar errores:
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest"); // Error: sayHi no es una funcin
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Error, la llamada sayHi anidada ya no funciona!
Eso sucede porque la funcin toma sayHi de su entorno lxico externo. No hay sayHi local, por lo que se utiliza la variable externa. Y en el momento de la llamada, ese sayHi externo es nulo.
El nombre opcional que podemos poner en la Expresin de funcin est destinado a resolver exactamente este tipo de problemas.
Usmoslo para arreglar nuestro cdigo:
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // Ahora todo va bien
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Hello, Guest (la llamada anidada funciona)
Ahora funciona, porque el nombre "func" es una funcin local. No se toma desde el exterior (y no es visible all). La especificacin garantiza que siempre har referencia a la funcin actual.
El cdigo externo todava tiene su variable sayHi o welcome. Y func es el nombre de funcin interna con el que la funcin puede llamarse a s misma de manera confiable.
La caracterstica nombre interno descrita aqu solo est disponible para Expresiones de funciones, no para Declaraciones de funciones. Para las declaraciones de funciones, no hay sintaxis para agregar un nombre interno.
A veces necesitamos un nombre interno confiable, este es un motivo para reescribir una Declaracin de funcin en una Expresin de funcin con nombre.
Resumen
Las funciones son objetos.
Aqu cubrimos sus propiedades:
nameEl nombre de la funcin. Por lo general, se toma de la definicin de la funcin, pero si no hay ninguno, JavaScript intenta adivinarlo por el contexto (por ejemplo, una asignacin).lengthEl nmero de argumentos en la definicin de la funcin. Los parmetros rest no se cuentan.
Si la funcin se declara como una Expresin de funcin (no en el flujo de cdigo principal), y lleva el nombre, se llama Expresin de Funcin con Nombre (Named Function Expression). El nombre se puede usar dentro para hacer referencia a s mismo, para llamadas recursivas o similares.
Adems, las funciones pueden tener propiedades adicionales. Muchas bibliotecas de JavaScript conocidas hacen un gran uso de esta funcin.
Crean una funcin principal y le asignan muchas otras funciones auxiliares. Por ejemplo, la biblioteca jQuery crea una funcin llamada $. La biblioteca lodash crea una funcin _, y luego agrega _.clone, _.keyBy y otras propiedades (mira los docs cuando quieras aprender ms sobre ello). En realidad, lo hacen para disminuir su contaminacin del espacio global, de modo que una sola biblioteca proporciona solo una variable global. Eso reduce la posibilidad de conflictos de nombres.
Por lo tanto, una funcin puede hacer un trabajo til por s misma y tambin puede tener muchas otras funcionalidades en las propiedades.
Comentarios
<code>, para varias lneas envolverlas en la etiqueta<pre>, para ms de 10 lneas utilice una entorno controlado (sandbox) (plnkr, jsbin, codepen)