Para mí, Javascript es un lenguaje muy especial. Todos los que llevamos un par de años en esto de la programación, a lo largo de nuestros estudios y de nuestra vida profesional hemos tenido la oportunidad y la necesidad de aprender o familiarizarnos con algunos cuantos lenguajes de programación. Cada uno de estos lenguajes, tienen sus ventajas y sus inconvenientes, detalles que nos han encantado y otros que nos han sacado de nuestras casillas. Dentro de los que yo me he tropezado en mi camino, Javascript tiene un lugar especial ¿Por qué digo esto?:
- Es un lenguaje interpretado. Los lenguajes interpretados tienen su propia idiosincrasia.
- Se concibió para ejecutarse en el navegador (que es un entorno de ejecución ciertamente peculiar) aunque ya ha dado el salto para ejecutarse en el servidor.
- Su estructura sintáctica es muy abierta. Te ofrece ciertas construcciones, pero muy abiertas. Esta sintaxis tan abierta junto con el hecho de ser interpretado te da una enorme expresividad… pero puede suponer un infierno para depurarlo o para mantenerlo (cuando el tamaño del código crece).
(Por supuesto, estos son opiniones particulares, estaré encantado si alguien no comparte mi opinión).
Dentro de esta flexibilidad de su estructura, para mí uno de los elementos más fascinantes son las funciones. Si tuviera que resumir en una frase el motivo por el que son tan fascinantes sería «porque las funciones javascript son objetos de primer orden».
Las funciones javascript como objetos de primer orden
¿Qué quiero decir con esta frase? A ver si me consigo explicar:
- En la mayoría de lenguajes que me he encontrado, las funciones son una estructura a la que se asocia un identificativo, que te permite encapsular un trozo de código y que se puede invocar usando el identificativo asociado y pasándole una serie de parámetros definidos en la declaración.
- En Javascript, podemos declarar funciones al «estilo clásico» que he mencionado antes … pero como una función es un objeto de tipo
function
… también podemos declararla de otro modo alternativo ligeramente distinto.
Así, una función hello
podemos declararla de dos formas distintas:
Al «estilo clásico»:
1 2 3 4 |
function hello(name) { return 'hello, ' + name; } console.debug(hello('jmj')); // hello, jmj |
Pero también podemos declararla así:
1 2 3 4 |
var hello = function(name) { return 'hello, ' + name; }; console.debug(hello('jmj')); // hello, jmj |
En esencia es casi lo mismo … pero no es exactamente igual:
Esto, funciona correctamente:
1 2 3 4 |
console.debug(hello('jmj')); // hello, jmj function hello(name) { return 'hello, ' + name; } |
Pero esto no funciona:
1 2 3 4 |
console.debug(hello('jmj')); // hello, jmj var hello = function(name) { return 'hello, ' + name; }; |
El motivo por el que no funciona es que en la primera versión, el intérprete de Javascript la convierte por nosotros:
1 2 3 4 |
var hello = function(name) { return 'hello, ' + name; }; console.debug(hello('jmj')); // hello, jmj |
(O sea, el intérprete define la variable hello
al principio del código).
Pero en nuestra segunda versión nosotros hemos definido la variable hello
manualmente, por lo que el intérprete no modifica nada … pero como la hemos invocado previamente a su definición… pues la ejecución falla.
Importante: cuando declaramos una función al «estilo clásico», no la terminamos con ;
. Pero cuando la definimos como una variable, sí que terminamos la definición con ;
. Los intérpretes de Javascript suelen ser bastante permisivos y no le dan importancia a esos detalles. Pero creo recordar que IE8 se queja.
Creo que ha quedado claro que los términos «función» y “objeto tipo function
” son equivalentes en Javascript. A lo largo de esta entrada, emplearé el término «función» cuando la usemos de forma «clásica» y el término “objeto de tipo function
” para remarcar que es un objeto y que se puede manipular como cualquier otro objeto.
Distinguir entre manipular la variable de tipo function
y ejecutar la función asociada
Así, pues, tenemos variables que apuntan a objetos de tipo function
. Incluso cuando declaramos una función al «estilo clásico», ya hemos visto que estamos declarando variables que apuntan a objetos de tipo function
. Este dato, como veremos, nos puede dar mucha expresividad para nuestros programas. Pero es fundamental que tengamos claro qué es manipular la variable y qué es ejecutar la función referenciada por la variable. A ver si puedo explicar este punto con un ejemplo simple:
1 2 3 |
var hello = function() { return 'hello'; }; console.debug(hello); // function console.debug(hello()); // hello |
- En la línea 1, he definido una variable
hello
a la que he asignado un objeto de tipofunction
. - En la línea 2, he mostrado en la consola de depuración
hello
(que es la variable), y el valor mostrado es un objeto de tipofunction
. - En la línea 3, he mostrado en la consola de depuración
hello()
(o sea, he invocado a la función asignada a la variablehello
) y el valor mostrado es'hello'
.
Como veis, escribir unos simples ()
marcan una diferencia abismal a la hora de ejecutar el código.
Manejando objetos de tipo function
Como las funciones son objetos, eso quiere decir que puedo hacer con ellas cualquier tipo de operación que puedo hacer con cualquier otro tipo de objeto. Por ejemplo:
1 2 3 |
function hello() { return 'hello'; } var a = hello; console.debug(a()); // hello |
Efectivamente, puedo asignar a mi variable a
el valor de mi variable hello
. Con lo cual, tengo ambas variables referenciando al mismo objeto function
. Puedo usar hello()
o a()
para invocar a la misma función.
Array de funciones
1 2 3 4 5 6 7 8 9 10 11 |
var hit = function(name) { return 'hit ' + name; }; var myFuncs = []; myFuncs.push(hit); myFuncs.push(function(name) { return 'push ' + name; }); myFuncs.push(function(name) { return 'shoot ' + name; }); myFuncs.push(function(name) { return 'slash ' + name; }); for (var i=0; i<=myFuncs.length; i++) { myFuncs[i]('jmj'); } |
En este pequeño trozo de código, hemos definido un array. Y le hemos añadido cuatro objetos de tipo function
. El primero, lo hemos definido previamente y hemos usado la variable para añadirlo al array. Pero los tres siguientes los hemos definido sobre la marcha cuando los añadíamos al array. Son funciones anónimas (acostumbraros a este tipo de operaciones, porque en Javascript las usaréis mucho).
Una vez añadidas nuestros objetos function
al array, lo hemos recorrido e invocado a cada uno de los elementos. Fijaros en la nomenclatura: myFuncs[i]('jmj');
:
- Accedo al elemento
i
de mi arraymyFuncs
(myFuncs[i]
). - Una vez que he accedido, lo ejecuto pasándole «jmj» como argumento
('jmj')
.
Los objetos tipo function
pueden ser argumentos pasados a otras funciones
Veamos este nuevo fragmento de código:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function parseArray(myArray, myParser) { for (var i=0; i<myArray.length; i++) { myArray[i] = myParser(myArray[i]); } return myArray; } // version 1 function parser(s) { return s.toUpperCase(); } console.debug(parseArray([a, b, c], parser)); // ['A, 'B', 'C'] // version 2 console.debug(parseArray([a, b, c], function(s) { return s.toUpperCase(); } )); // ['A, 'B', 'C'] |
Unos cuantos detalles:
- En la versión 1, cuando invocamos a
parseArray
, le pasamos como argumentoparser
(el nombre de la variable que referencia al objeto de tipofunction
) y noparser()
(si hiciéramos esto último, lo que haríamos es ejecutar la función). - La versión 2 es lo mismo que la versión 1 excepto que le pasamos como argumento una función anónima (¿para qué declarar la función previamente, si sólo vamos a usarla una vez y es una función sencillita de declarar? nos ahorramos la declaración previa).
Pasar objetos function
como argumentos a funciones que se ejecutan asíncronamente
El hecho de poder pasar objetos function
a funciones se usa extensamente en Javascript. El motivo es que muchas de las operaciones que realizamos en nuestros programas Javascript son operaciones asíncronas ¿Qué quiero decir con esto? Que pedimos que se ejecuten… pero no sabemos cuando se terminarán de ejecutar. Ejemplos de estas operaciones asíncronas son:
- Operaciones que afectan a comunicaciones (por ejemplo, todas las llamadas Ajax).
- Operaciones que manipulan el DOM.
El problema de las operaciones asíncronas (o sea, que no sé cuándo van a terminar) es que si necesito hacer una segunda operación supeditada a cuando terminen ¿cómo sé cuándo realizar esta segunda operación? Por ejemplo, a mí me gustaría escribir en mi código:
1 2 |
var resp = myAjaxCall(); // hago una llamada ajax que me devuelve un resultado resp process(resp); // paso el resultado a mi funcion resp |
Pero, como he dicho, esto no funciona: myAjaxCall()
se ejecuta… pero el programa pasa a la siguiente instrucción sin esperar el resultado de la operación, con lo que la llamada a process(resp)
no es válida porque es realizada sin que halla llegado el resultado de myAjaxCall()
y por lo tanto el valor de resp
es incorrecto.
¿Cómo hay que escribir este tipo de operaciones que esperan un resultado asíncrono?:
1 |
myAjaxCall(function(resp) { process(resp); }); |
A ver si puedo explicarlo:
- Realizo la llamada a
myAjaxCall
pero como sé que es una operación asíncrona que no sé cuándo termina, le pido a la llamada: “cuando termines, invoca al objetofunction
que te paso como parámetro”. - Cuando la llamada termine, invocará a este objeto
function
y le enviará como parámetro el valorresp
que es el resultado de la operación asíncrona. Ahora, dentro de mi objetofunction
sí puedo invocar a mi funciónprocess
y le puedo pasar este parámetroresp
que ahora sí que estará correctamente relleno (insisto, todo esto ocurrirá una vez que la operación asíncrona termine).
La gestión de las operaciones asíncronas, pueden ser muy complejo. El motivo de esta complejidad es que queremos responder a eventos que terminan asíncronamente en un futuro pero queremos responder con la información que disponíamos cuando solicitamos la operación. Y las condiciones ya no son las mismas (tendemos a pensar de forma secuencial, nos cuesta pensar de forma asíncrona). Por eso, para gestionar correctamente las operaciones asíncronas hay que tener en cuenta otros dos conceptos fundamentales de Javascript: el alcance de las variables y el contexto en que se ejecutan las funciones (no explico más sobre ellos aquí, pero no tengo más remedio que al menos citar ambos conceptos).
Las funciones pueden devolver objetos de tipo function
Una función Javascript puede devolver un resultado (como cualquier otro lenguaje de programación). Pero como el tipo function
es un tipo válido… puedo devolver como resultado una función. Veamos el siguiente código:
1 2 3 4 5 6 |
var multiplier = function(multiplier) { return function(number) { return multiplier * number; }; }; var double = multiplier(2); var triple = multiplier(3); console.debug(double(2)); // 4 console.debug(triple(2)); // 6 console.debug(multiplier(4)(2)); // 8 |
Este ejemplo es muy simple (el objetivo, es mostrar la operativa de la forma más clara posible) pero podemos realizar operaciones más complejas:
1 2 3 4 5 6 7 8 9 10 11 12 |
var count = 2; function counter(initValue) { var count = initValue; return function(value) { count += value; return count; }; } var myCounter = counter(0); console.debug(myCounter(3)); // 3 console.debug(myCounter(4)); // 7 console.debug(count); // 2 |
Javascript no cuenta con elementos constructivos como las variables estáticas de otros lenguajes de programación. Pero podemos conseguir resultados similares usando los mecanismos con los que sí cuenta. En este caso:
- La función
counter
tiene una variablecount
. Devuelve un objetofunction
que cuando se invoque a) admite un parámetrovalue
b) incrementará la variablecount
con este valor c) devolverá el nuevo valor decount
. - Lo más importante, es que la variable
count
declarada dentro decounter
es absolutamente privada e inaccesible para el resto del código. Si pido el valor de la variablecount
, me devuelve el valor de la variable declarada fuera decounter
.
A mí, personalmente, me gusta usar los recursos que me da cada lenguaje para indicar qué variables son de uso privado y cuáles son de uso global. Con construcciones como ésta, puedo conseguirlo en Javascript.
Funciones invocadas inmediatamente
Lo he traducido al castellano pero su denominación en inglés es «immediately-invoked function expression» (también conocidas como «IIFE»). Y ¿qué es lo que son?. Creo que lo mejor es ver un ejemplo y explicarlo sobre el ejemplo:
1 2 3 4 5 6 7 8 |
var a = 'hello, world'; (function () { var a = 'hello, jmj'; console.debug(a); // hello, jmj })(); console.debug(a); // hello, world |
¿Qué es lo que he hecho?:
- Entre las líneas 3 a 6 he definido una función anónima. Dentro de dicha función, declara e inicializa una variable
a
. - Pero lo más interesante es que esa función la rodeo de paréntesis y le añado paréntesis al final (
(function() {...})()
) ¿Recordáis cuando antes explicaba la diferencia entre escribirvariableQueApuntaAFuncion
yvariableQueApuntaAFuncion()
. Efectivamente, al añadir los()
al final lo que hago es ejecutar la función anónima nada más declararla.
«¿Y para qué quiero hacer esto, declarar una función anónima y ejecutarla directamente?» Fijaros que tengo dos variables con el mismo nombre a
, una declarada dentro de la función anónima y otra declarada fuera. Cada una, tiene su propio valor y ninguna afecta a la otra. Son fragmentos de código absolutamente independientes.
«Bueno, sí, son fragmentos de código independientes ¿Y qué?». Este hecho, hay que considerarlo en el contexto del tamaño de tus programas Javascript:
- Si el programa es pequeño y tú eres el único programador, es fácil conseguir que no haya conflictos con los nombres de las variables (por repetir, por ejemplo, el mismo nombre en dos fragmentos de tu código).
- Pero si el programa es extenso o participan varios programadores o tienes que usar librerías de terceros, conseguir que no tengamos conflictos con los nombres de las variables y de las funciones, es un poco más complicado.
- Si encapsulas tu código dentro de una IIFE, estás garantizando que las variables y funciones que declares dentro no afectarán al resto del código.
Para explicar este punto de forma más completa, debería hablar más extensamente sobre el alcance de las variables en Javascript… pero de momento me tengo que limitar a estas pequeñas pinceladas.
Puedes añadir objetos tipo function
como propiedades a cualquier otro objeto
Una de las peculiaridades de Javascript es que en cualquier momento puedes extender una variable que no sea de tipo primitivo, añadiéndole propiedades sobre la marcha:
1 2 3 4 |
var myBirthday = new Date(1967,1,9); myBirthday.name = 'jmj birthday'; console.debug(myBirthday) // Thu Feb 09 1967 00:00:00 GMT+0100 (CET) console.debug(myBirthday.name); // jmj birthday |
Lo mismo que le hemos añadido a este objeto myBirthday
una propiedad name
que hemos inicializado con una cadena, podíamos haberlo inicializado con un objeto de tipo function
:
1 2 3 4 5 6 7 8 9 |
var myModule = {}; myModule.name = 'jmj'; myModule.greeting = function() { return 'hello ' + myModule.name; }; var name = 'rufus'; var greeting = function() { return 'grrrr I am ' + name; }; console.debug(myModule.greeting()); console.debug(greeting()); |
En este pequeño ejemplo, hemos creado un objeto myModule
(inicialmente vacío) y le hemos añadido dos propiedades name
y greeting
(si fuera un lenguaje clásico de programación, esta última propiedad podríamos considerarla como un método). Después, hemos declarado dos variables name
y greeting
. Lo importante, es que no hay conflictos entre ellas aunque tienen los mismos nombres.
Usando esta técnica, también podemos encapsular nuestro código (variables y funciones) dentro de un solo objeto (al que podemos considerar un módulo) para evitar tener conflictos con el código de terceros.
Podemos usar funciones para crear nuevos objetos
Javascript no tiene un constructor para definir clases. Pero podemos crear nuevos objetos que tengan un comportamiento predefinido:
1 2 3 4 5 6 7 8 |
function Person(name) { this.name = name; this.greetings = function() { return 'hi my name is ' + this.name; } } var jmj = new Person('jmj'); var brutus = new Person('brutus'); console.debug(jmj.greetings()); // hi my name is jmj console.debug(brutus.greetings()); // hi my name is brutus |
Sin entrar en muchos detalles:
- Uso la función
Person
para definir que los objetos tienen una propiedadname
y un métodogreetings
. - Uso el operador
new
para crear dos objetos llamadosjmj
ybrutus
ambos basados enPerson
. - Puedo usar las propiedades y métodos predefinidos por
Person
en ambos objetos.
Fijaros que no escribo “jmj
es de tipo Person
” sino que escribo “jmj
está basado en Person
”. Lo que intento es dejar claro que Javascript no permite definir clases pero al menos permite al crear objetos a partir de un patrón predefinido. Y sólo quiero mencionar que dicho patrón es una función (explicar detenidamente este concepto, requeriría una o dos entradas sólo para ello).
¿Conclusiones?
Tal como mencionaba al comienzo de la entrada, Javascript es un lenguaje relativamente simple. Pero las construcciones que tiene permiten muchísima expresividad. En esta entrada, he intentado recopilar todas las manipulaciones con objetos de tipo function
que me he ido encontrando (y es posible que se me olvide alguna y haya otras más que no conozca). Ahora sí, es nuestra resposabilidad como programadores el usar esta expresividad para escribir mejores programas Javascript.
Muy interesante y bien explicado para los que estamos empezando con el tema.
Gracias por compartir.
Un saludo.
Gracias!!! Me alegro que te haya gustado. Realmente, sólo he arañado la superficie. Pero creía interesante un artículo recopilatorio para por lo menos tener lo que yo llamo la «visión de conjunto». Después, será tu día a día como programador Javascript y las necesidades a las que te enfrentes las que decidan cuáles de estos puntos te son especialmente útiles («esto me suena de algo..») y entonces ya te animes a profundizar en ese punto concreto. Por ejemplo:
Buenos días,
Muy buena aportación.
¿Puedes explicar un poco ésto?
var multiplier = function(multiplier) { return function(number) { return multiplier * number; }; };
console.debug(double(2)); // 4
console.debug(multiplier(4)(2)); // 8
Entiendo el caso de (4)(2) en el que se pasan parámetros a dos funciones. Supongo que (4) se pasa como parámetro multiplier, y 2 como number. ¿Es así o al revés?
No entiendo que al pasar parámetros a una única función se copie a las dos.
En mi opinión, tanta libertad a la hora de programar genera más problemas que beneficios… o es que vengo del Pascal (Delphi) 😉
Efectivamente, cuando ejecutas:
multiplier(4)(2)
– El valor 4 se pasa como parámetro multiplier
– El valor 2 se pasa como parámetro number
Primero, se invoca multiplier(4) y al resultado de esta invocación se la invoca pasándole el parámetro 2
«No entiendo que al pasar parámetros a una única función se copie a las dos.»
Creo que tienes que pensarlo del siguiente modo: estamos encadenando dos invocaciones seguidas (esto puedo hacerlo porque la primera función me devuelve un objeto function):
– Paso 1: Javascript primero ejecuta multiplier(4) (no le presta atención -de momento- a lo que viene a continuación)
– Paso 2: y al resultado de esta primera invocación le aplica (por fin) (2)
Es como si lo hubiéramos hecho en dos pasos:
// paso 1
var nuevaFuncion = multiplier(4);
// paso 2
console.debug(nuevaFuncion(2)); // 8
«En mi opinión, tanta libertad a la hora de programar genera más problemas que beneficios… o es que vengo del Pascal (Delphi)»
Bueno, personalmente, siempre que he tenido la necesidad de aprender un lenguaje nuevo, creo que es fundamental dedicarle tiempo a estudiar sus peculiaridades, lo comparas con otros lenguajes que conoces (para ordenar tus pensamientos) y al final intentas aprovechar sus ventajas y convivir con sus inconvenientes (y te lo cuenta alguien que comenzó con la programación procedural y de ahí pasó a la programación orientada a objetos, por mi cuenta y riesgo)
Suerte con Javascript 🙂