Introducción a las funciones en JavaScript

Las funciones en un lenguaje de programación son un recurso básico cuyo objetivo principal es la reutilización de código. Supongamos que tuviésemos que realizar un cálculo para devolver un valor a partir de unos datos iniciales, proceso que ocuparía una cantidad importante de líneas de código. Si sólo se ejecutara una única vez no sería necesario diferenciar ese trozo de código. Pero sería más eficiente incluirlo en una función si luego fueramos a ejecutarlo para otros valores iniciales en otro punto de nuestro programa.

Y aunque sólo se utilizara una única vez quizás es preferible separarlo en una función, puesto que mejora la legibilidad de nuestro código, evitando en la medida de lo posible el efecto código espaguetti. Esto se produce cuando tenemos un elevado número de líneas de código que entorpecen la lectura, porque en el fondo el código lo ejecutan las máquinas, pero lo leen los humanos. Y por leerlo entendemos acciones como la depuración de errores, la incorporación de mejoras o la actualización a nuevas versiones del lenguaje.

En otro lenguajes se diferencia entre procedimientos y funciones. Los primeros ejecutan una acción y los segundos devuelven un valor. En JavaScript sólo tenemos funciones para ejecutar y, siempre, devolver un valor. Para devolver un valor explícitamente se antepone la sentencia return. En caso de que no se utilice devolverá por defecto undefined. Recuerda que éste es otro de los objetos built-in de JavaScript.

Se dice que JavaScript tiene funciones de primera clase porque en el fondo las funciones son objetos y, por tanto, podemos pasarlos como argumentos de otra función o bien devolverlos desde una función. Esto sumado a otras características como las funciones anónimas, anidamiento de funciones, closures o los IIFE le dan el carácter de lenguaje funcional que permite realizar más cosas de las que pudiéramos suponer en principio.

En relación con las funciones, ECMAScript6 (ES6) trae muchas novedades muy interesantes:

  • Argumentos por defecto.
  • Operador ... para indicar resto de argumentos.
  • Funciones flecha.
  • Generadores.

Quiero ponerme al día con esas nuevas características. Pero antes en este tema haré un repaso general sobre las funciones con el objeto de dar a entender la importancia de las mismas para un uso potente de JavaScript.

Aclaración sobre los ejemplos de esta página

En estos temas que exponen nuevas cosas de ES6 he decidido hacer los ejemplos usando ya características ES6. Y siempre que sea posible en modo estricto. Al menos cosas que se soporten en las versiones últimas de los navegadores principales. Con versiones no recientes puede que no funcionen, en cuyo caso se advertirá en cada ejemplo con un mensaje de error. Puede ver el soporte actual en Kangax ECMAScript 6 support.

Cuestiones básicas sobre funciones

En este código de ejemplo puede encontrar una declaración de función donde se distingue el nombre sumar, sus argumentos (a, b) y la devolución de un valor con return. Posteriormente a la declaración podemos llamar o invocar dicha función con los valores requeridos para sus argumentos.

function sumar(a, b){
    return a+b;  
}
let c = sumar(1, 2); // c = 3
    

Es muy importante entender que una función es finalmente un objeto, y como tal podemos crearlas a través de un constructor. En el tema sobre Strings en JavaScript ES6 vimos que al final toda cadena se crea con el constructor String(). Con las funciones sucede lo mismo, toda función se crea con el constructor Function(). Aqui podemos usar directamente su constructor:

let sumar = new Function("a", "b", "return a+b");
let c = sumar(1, 2); // c = 3
    

Pasando todos los argumentos como tipos string, donde los primeros son, a su vez, los nombres de los argumentos de la función a construir y el último es el cuerpo de código. Al igual que con el constructor String() podemos obviar la palabra reservada new, así que new Function() hace lo mismo que Function(). Las dos creaciones de la función sumar() son iguales.

Al igual que para String() y el resto de tipos de JavaScript, hemos de evitar usar el constructor pues es menos eficiente. El primer inconveniente es que JavaScript no evalúa los argumentos y el código del cuerpo de la función hasta que ejecutemos la sentencia new Function(), así que no detectaremos errores de sintaxis hasta ese momento. Otro problema es que las funciones creadas sólo tienen un alcance global. Más abajo incidiremos más en este aspecto al hablar de la cadena de alcances.

Las funciones como constructores de variables

Una de las finalidades de las funciones en JavaScript es hacer de constructores de objetos. Tenemos el siguiente ejemplo que extrae todo lo que hay en el objeto subyacente tras cualquier variable. Con la opción literales activada declararemos una variable literal, es decir, algo como x = "abc", mientras que desactivada lo haremos con su constructor x = new String("abc"). Para las funciones equivale a una declaración de función sin o con el constructor respectivamente.

Ejemplo: Extrayendo propiedades de las variables

Tipo

Crearemos una variable de ese tipo

Este ejemplo no usa ES6 ni modo estricto, debido a limitaciones a la hora de obtener arguments y caller para una declaración de función en Chrome. También me interesa ver como se ejecuta en IE11 que tiene muy bajo soporte. Puedes consultar el código JS original de este ejemplo.
Figura
Figura. Una instancia de String con su prototipo __proto__.

Empecemos con la variable de tipo string que se sustenta en el objeto String. En la Figura podemos ver una captura en el Developer Tools de un navegador. Al igual que para Number, Boolean, Array y Object, los objetos contienen la propiedad __proto__ que es el prototipo desde el que se construyó dicho objeto. Para String esta herramienta nos da el [[PrimitiveValue]] que viene a ser el valor de la variable. En nuestro ejemplo obtenemos ese valor con el método valueOf().

El prototipo __proto__ de las variables, que son como cualquier otra cosa objetos, nos indica el tipo de dicha variable. En el ejemplo vemos que al desplegar sus valores obtenemos todos los métodos de String, tal como ya vimos en el tema Strings en JavaScript. Podemos seguir recursivamente desplegando prototipos hasta llegar al objeto Null, último objeto en la cadena de prototipos. En cada prototipo __proto__ de las variables mecionadas vemos el constructor. Y éstos constructores son funciones que crean los objetos que sustentan las variables. Sus nombres son también los respectivos nombres de los tipos de variables que crean.

Vemos en el constructor su propiedad prototype, diferente de __proto__. El __proto__ es el prototipo de una variable a partir del cual un constructor creó dicha variable. Todos los objetos que sustentan variables en JavaScript tiene un __proto__. Mientras que prototype es el prototipo a partir del cual un constructor creará una variable. Sólo las funciones tiene este prototype, pues sólo las funciones pueden crear objetos. Puede ahondar más en el tema Entendiendo el prototipo en JavaScript.

Podrá observar que el prototype del constructor String es la cadena vacía "", el de Number es el 0, el de Boolean es el valor false, el de Array es un array vacío [] y, finalmente, el de Object es [object Object] que es la estructura básica de un objeto de JavaScript.

Figura
Figura. Una función en JavaScript

Por eso las funciones, como vemos en la Figura, tiene ambos __proto__ y prototype. El primero es el prototipo a partir del cual se creó dicha función. Y el segundo es el prototipo a partir del cual dicha función creará nuevos objetos. Con la opción del ejemplo para crear un Objeto con función, verá que usando la función constructora miConstructor() creamos un objeto que asignamos a la variable obj. En ese ejemplo dotamos al constructor de una miPropiedad y un miMetodo. Luego asignamos al prototype otro método. Al construir el nuevo objeto vemos que el primer método es propiedad de la instancia mientras que el asignado al prototipo podrá ser compartido por todas las instancias que se creen a partir de ese constructor. Esto es un aspecto muy importante cuya ventaja también explicamos en el tema Agregando métodos en el prototipo.

Tal como se explica en el tema Objetos en JavaScript, la facilidad para crear objetos con funciones en JavaScript nos permite emular la Programación Orientada a Objetos (POO). Aunque ES6 incorpora ahora clases, en el fondo se basan estructuralmente en ese funcionamiento.

Sentencias, declaraciones y expresiones en JavaScript

Una cuestión básica para entender las funciones es diferenciar entre declaración de función y expresión de función. Pero antes debemos saber la diferencia entre declaración y expresión en JavaScript.

Un programa en JavaScript es una lista de sentencias y declaraciones. Las sentencias y declaraciones llevan a cabo acciones, por ejemplo: un bucle while, un condicional if, una declaración de variable let, una declaración de clase class o una declaración de función function.

Por lo tanto una sentencia o declaración empieza con alguna de esas palabras reservadas. Las sentencias se separan con punto y coma. También es una sentencia un bloque determinado por {}. Los bloques no necesitan separarse con punto y coma. Los bloques pueden forman parte de algunas sentencias como los condicionales, bucles o declaraciones de funciones. Pero también es posible un bloque independiente para albergar sentencias. Si además es un bloque etiquetado podemos salirnos del mismo con un simple break:

//Un bloque independiente puede servir para aislar
//variables let o const del resto del código
{
    let miVarBloque = 1;
    console.log(miVarBloque); // 1
}
try {
    console.log(miVarBloque);
} catch(e){
    console.log(e.message); //miVarBloque is not defined
}
//Un bloque etiquetado nos permite
//salir del mismo con break
bloque: {
    console.log("Con break"); // "Con break"
    break bloque;
    console.log("Sin break"); // Esto no se ejecuta
}
    

Por otro lado una expresión devuelve un valor. Algo como "abc" o Math.pow(x, 2) son expresiones que devuelven valores. También es una expresión una función anónima function(){}; cuando se asigna a una variable. ¿Qué es lo que habría entonces en esa variable? Analicemos este código:

let x = function(){}; // Usando una expresión de función
/* O alternativamente una declaración de función
function x(){}; */
console.log(x); // function(){}
console.log(typeof x); // "function"
console.log(x instanceof Function); // true
let y = x();
console.log(y); // undefined
console.log(typeof y); // "undefined"
    

En la variable x tenemos una instancia del objeto intrínseco Function, a partir del cual JavaScript creo la instancia que asignó a la variable. En x hay, por tanto, un valor de tipo function. Si ejecutamos esa función con y = x() veremos que en y tenemos ahora un tipo undefined de un valor undefined, pues eso es lo que devuelve por defecto cualquier función sin una sentencia return. Recuerde que undefined es también un objeto intrínseco de JavaScript, cuyo tipo se llama igual.

Por lo tanto para diferenciar una sentencia de una expresión tenemos que preguntarnos si hace algo o devuelve algo. Si en la primera línea del código anterior hubiésemos usado una declaración de función (una sentencia) como function x(){};, el resultado hubiese sido equivalente en tipos e instancias. Aunque, como veremos en un apartado posterior hay unas sutiles diferencias que no se perciben en ese código de ejemplo.

Además pueden haber sentencias de expresión, que son expresiones que por sí mismas forman una sentencia. De hecho una simple expresion en una línea de programa como "abc"; es una sentencia de expresión, con lo que JavaScript construirá una instancia del objeto String para ese valor. Aunque por supuesto, para poco servirá dado que no se asigna a ninguna variable. En una expresión de asignación como x = "abc"; encontramos el identificador en su parte izquierda y la propia expresión en su parte derecha. Las expresiones también se esperan en los argumentos de una función o para rellenar un array literal ["abc", Math.pow(x,2)]. Si encontramos {} donde se espera una expresión será entonces un objeto literal y no un bloque.

Por lo tanto donde JavaScript espere una sentencia puede haber una expresión, pero no al revés. Por ejemplo, en un argumento de función o en la parte derecha de una asignación se espera una expresión y no puede contener una sentencia. Un caso especial es la sentencia condicional if que puede codificarse con una expresión mediante un operador condicional:

let x = 1;
//Sentencia IF
if (x==1) {
    console.log("true"); // "true"
} else {
    console.log("false");
}
//Expresión de sentencia OPERADOR CONDICIONAL
console.log((x==1)?"true":"false"); // "true"
    

Esto es porque (x==1)?"true":"false"; es una expresión que devuelve un valor, porque es imprescindible que en sus dos opciones hayan exactamente expresiones y no sentencias en este caso. Pero una expresión podría ser alert(1) de tal forma que si tuviésemos (x==1)?alert(1):"false"; ejecutaría esa función y devolvería undefined, dado que alert() sólo presenta un mensaje en pantalla sin ninguna devolución explícita.

Por otro lado una sentencia de expresión no puede empezar por {, let, class o por function porque serían entonces sentencias tal como vimos antes. Los siguientes ejemplos con comentarios aclaran más cosas sobre las expresiones:

let x = 1;

//Estas expresiones, como sentencias, se ejecutan sin producir 
//ningún error, aunque por supuesto no sirven para mucho pues 
//esos valores no van a ningún sitio
123;
"abc";
[1, 2, 3];
x+1;
(x==1)?"true":"false";

//Pero esta expresión, también como sentencia, si que hace 
//algo: incrementa en una unidad el valor de la variable
x++;


//El operador "," separa expresiones. Con cada coma o punto
//y coma javaScript va resolviendo los valores 
x+1, "abc";

//Los paréntesis agrupan expresiones separadas por comas, se 
//ejecuta cada una y se devuelve la última. En este caso se 
//presenta el mensaje de alerta y asigna "xyz" a la variable.
let y = (alert(1), "xyz");
console.log(y);  // "xyz"

//Hay expresiones que necesitan encerrarse entre paréntesis 
//obligatoriamente pues sino tendríamos un error de sintaxis:

//Un objeto literal para diferenciarlo de un bloque
({a: 1, b: 2});

//Una expresión de función para evitar que no sea una 
//declaración de función errónea por no tener nombre
(function(){});

//Porque con nombre esto no es una expresión sino una sentencia 
//de declaración de función, que debe llevar obligatoriamente 
//un nombre
function fun(){};

En el código anterior comentamos el uso del operador coma para separar expresiones. Puede ver más en este apartado el operador coma de JavaScript de un tema sobre closures que ya había publicado hace tiempo.

Creando funciones

Sin contar con el constructor Function() que ya vimos que no es recomendable usarlo, tenemos dos formas de crear una función. Una es mediante la declaración de función:

//Una declaración de función
function fun(){
    return 1;
}
console.log(fun()); // 1
console.log(typeof fun); // "function"
    

Y otra es mediante una expresión de función, donde usamos una función anónima:

//Una expresión de función (anónima)
let x = function (){
    return 1;
};
console.log(x()); // 1
console.log(typeof x); // "function"
    

La función anterior no tiene nombre, pero accedemos a ella a través de la variable a la que se asignó. Sin embargo es posible también ponerle un nombre, con lo que tenemos una expresión de función nominada:

//Una expresión de función nominada
let x = function fun2 (){
    console.log(typeof fun2); // "function"
    return 2;
};
console.log(x()); // 2
console.log(typeof x); // "function"
console.log(typeof fun2); // "undefined"
    

El nombre dado a la función (fun2) tiene un alcance limitado al cuerpo de la misma, como se observa que no es accesible con ese nombre externamente. El nombre interno fun2 podría tener una utilidad en algoritmos recursivos, como el siguiente para calcular el factorial de un número:

let factorial = function fact(n) {
    if (n>1) {
        return n * fact(n-1);
    } else {
        return 1;
    }
};
console.log(factorial(7)); // 5040
    

Anidamiento de funciones y alcance de variables

Las declaraciones de funciones son evaluadas antes que cualquier otra cosa en el cuerpo de código donde se encuentren. En el siguiente ejemplo vemos que podemos ejecutar la función antes de su declaración.

console.log(fun()); // 1
function fun(){
    return 1;
}
    

Pero si usamos una expresión de función nos díra que la función aún no está definida.

console.log(fun()); // fun is not defined
let fun = function (){
    return 1;
}
    

Las declaraciones de funciones pueden anidarse. Las funciones internas buscan las variables desde dentro hacia afuera, es decir, subiendo por la cadena de alcances. Primero la buscará entre sus argumentos y el propio cuerpo de la función, conocidas como variables locales a la propia función. Si no la encuentra la buscará en la función padre, y así hasta alcanzar el cuerpo del script donde se ubican las variables globales.

let x = 1;
function f(){
    let x = "abc";
    function g(){
        return x; // "abc"
    }
}
    

A veces es necesario el anidamiento de funciones para estructurar mejor nuestro código. Veámos el siguiente ejemplo donde vamos a calcular el perímetro de una serie de puntos. Los iniciales se corresponden con los cuatro vértices de un cuadrado, con el vértice inferior izquierdo en el origen de coordenadas. Para hacerlo genérico calculamos la distancia entre dos puntos y luego vamos sumando todas las distancias parciales. El recorrido podría cerrar el último punto con el primero, pero sería opcional.

function calcularPerimetro(puntos, cerrado){
    function calcularDistancia(a, b){
        return Math.sqrt(Math.pow(b.x-a.x, 2) + Math.pow(b.y-a.y, 2));
    }
    let suma = 0;
    for (let i=0, maxi=puntos.length-1; i<maxi; i++){
        suma += calcularDistancia(puntos[i], puntos[i+1]);
    }
    if (cerrado) {
        suma += calcularDistancia(puntos[puntos.length-1], puntos[0]);
    }
    return suma;
}
    

El cálculo de la distancia es necesario dentro del bucle y, en su caso, si el recorrido de puntos es cerrado. Por lo que podríamos declarar una función interna, anidada con la exterior, para no repetir ese cálculo en esos dos puntos del código. El ejemplo es muy simple, pero si el cálculo que hubiese que repetir fuese más largo y/o en muchos puntos del código, estaría justificado usar una función interna para evitar la repetición de código.

Ejemplo: Anidamiento de funciones

Este ejemplo usa ES6 y modo estricto. Puedes consultar el código JS original de este ejemplo.

Usar funciones dentro de otras funciones es muy frecuente en JavaScript. Pero debemos entender que las funciones son también objetos como todo en JavaScript. Y que los objetos no son creados hasta que se necesiten. Así que la función anidada del ejemplo calcularDistancia() es creada cada vez que ejecutemos la función exterior calcularPerimetro() ¿Qué diferencia de rendimiento habría si pusieramos la función anidada por fuera, al mismo nivel que la función calcularPerimetro()?

Vamos a hacer un test para comprobarlo usando un ejemplo más simple. Por un lado tenemos una función a1() que anida en su interior nueve funciones más desde a2() hasta a10(). Cada función devuelve un número y luego la primera función suma todos esos números:

//TEST con 9 funciones ANIDADAS dentro de  a1() que
//llamamos repetidas veces en un tiempo determinado
function a1(){
    function a2(){
        return 2;
    }
    //... más funciones
    function a10(){
        return 10;
    }
    return 1+a2()+a3()+...+a10();
}
    

En el segundo test ponemos las diez funciones b1() hasta b10() al mismo nivel, sin anidar. En este caso ejecutamos b1():

//TEST con 10 funciones NO ANIDADAS y ejecutamos b1()
//repetidas veces en el mismo tiempo que el test anterior
function b1(){
    return 1+b2()+b3()+...+b10();
}
function b2(){
    return 2;
}
//... más funciones
function b10(){
    return 10;
}
    

Tomando un tiempo determinado de 1000 ms ejecutaremos a1() y luego b1() contanto el número de veces que se ejecutan. Cuantas más veces se ejecuten durante el mismo tiempo mejor será el rendimiento:

function ejecutar(anidadas) {
    var iteraciones = 0;
    var ini = new Date();
    while (new Date() - ini < 1000){
        iteraciones++; 
        var rr = (anidadas) ? a1() : b1();
    }
    return iteraciones;
}
    

Ejemplo: Test rendimiento funciones anidadas

Durante ese tiempo ejecutaremos a1() para las anidadas y b1() para las no anidadas. Cuanto más iteraciones con el mismo tiempo mejor será el rendimiento.

Iteraciones (Cuanto mayor mejor)% mejora
AnidadasNo anidadasDiferencia
 
Este ejemplo no usa ES6 ni modo estricto porque quiero ver el rendimiento en IE11, que no soporta algunas cosas de ES6. Puedes consultar el código JS original de este ejemplo.

En la siguiente tabla puede ver los valores más frecuentes conseguidos en tres navegadores actuales en un tiempo de 1000 ms:

Navegador% mejora al no
anidar funciones
Chrome 4920%
Firefox 44<5%
IE 1130%

Es evidente que en Chrome 49 e IE 11 se consigue una mejora sustancial al no anidar funciones. Pero con Firefox 44 no sucede esto. Parece que de alguna forma optimiza el proceso de las anidadas, pues en ningún caso la mejora supera el 5%, siendo algunas veces cercanas a cero.

En general la anidación produce un código más estructurado, pues poco sentido habría en declarar de entrada todas las funciones que podría necesitar una página web. Dado que la declaración de una función comporta ubicar una zona de memoria para ella, entonces llenaríamos la memoria con un montón de objetos, muchos de los cuáles quizás ni lleguen a usarse pues podrían estar en condicionales de ejecución. Además hay algunas utilidades de la anidación de funciones que se basan precisamente en eso, como veremos en los siguientes apartados.

Expresión de función en un argumento de otra función

Una expresión de función puede incluirse en cualquier sitio donde quepa una expresión. Como en los ejemplos anteriores, puede ser en la parte derecha de una asignación. Pero también pueden ser el argumento de una función. Por ejemplo, el método addEventListener del DOM permite adjudicar un evento a un elemento, donde podemos usar una referencia a una función o una expresión de función. Esta función se le suele nombrar como manejador del evento:

//El argumento manejaEvento es una referencia a una función 
//que será llamada al hacer click sobre el elemento
elemento.addEventListener("click", manejaEvento, false);
//Y este sería el manejador del evento
function manejaEvento() {
    //Aquí hace algo al recibir el click    
}

//Pero también podríamos declarar directamente la función
//como una expresión de función
elemento.addEventListener("click", function(){
    //Aquí hace algo al recibir el click    
}, false);
    

En esta segunda forma se evidencia lo que comentamos en el apartado anterior sobre el anidamiento de funciones. La primera forma podría ser más eficiente si podemos compartir el mismo manejador para usar en distintos addEventListener repartidos por el código.

Pero con la primera forma no podemos pasar argumentos al manejador, solo cabe poner el nombre de la función sin argumentos. Por ejemplo, es posible usar un manejador global para varios eventos o elementos diferenciando situaciones. Usaríamos entonces una expresión de función en cada addEventListener que nos llame al manejador global con el argumento requerido:

elemento1.addEventListener("click", function(){
    manejadorGlobal(1);
}, false);
elemento2.addEventListener("click", function(){
    manejadorGlobal(2);
}, false);
function manejadorGlobal(tipo) {
    if (tipo == 1) {
        //Hacer algo
    } else if (tipo == 2) {
        //Hacer algo
    } else {
        //Comunicar error
    }
}
    

No debemos olvidar que addEventListener pasa implícitamente a la función manejadora del evento un argumento con el objeto event, un objeto de Window. Navegadores antiguos no pasaban ese argumento, por lo que había que referenciarlo con window.event dentro del manejador. De cualquier forma podríamos manejar un evento descubriendo de que elemento partió con event.target y así no usamos una expresión de función en la adjudicación del evento:

<div id="div1">haz click aquí</div>
<div id="div2">haz click aquí</div>
<script>
    let elemento1 = document.getElementById("div1");
    elemento1.addEventListener("click", manejadorGlobal, false);
    let elemento2 = document.getElementById("div2");
    elemento2.addEventListener("click", manejadorGlobal, false);
    //Esta función manejará los dos eventos
    function manejadorGlobal(event){
        //En navegadores como IE8 se accedía con window.event
        //y para soporte conjunto con otros solíamos hacer
        //var event = event || window.event;
        let id = event.target.id;
        if (id=="div1"){
            //hacer algo con este elemento
        } else if (id=="div2") {
            //hacer algo con este elemento
        } else {
            //comunicar error o hacer otra cosa
        }
    }
</script>
    

Expresión de función en sentencia return

Otro sitio donde JavaScript espera una expresión es con la sentencia return. Dado que las funciones son también objetos es posible devolverlas. Y esto, como veremos en apartados más abajo, confiere de una gran potencialidad a JavaScript. En el siguiente ejemplo tenemos una función exterior que se ejecuta para iniciar el contador. Se trata de fijar una fecha y hora de inicio y devolver una función que cuente el tiempo entre ese inicio y el momento en que se ejecute.

function iniciarContador(elemTiempo, elemInicio, elemAhora) {
    let fechaInicio = new Date();
    elemInicio.textContent = fechaInicio;
    elemAhora.textContent = "";
    elemTiempo.textContent = 0;
    //Devuelve la función para contar el tiempo
    return function(){
        let ahora = new Date();
        elemAhora.textContent = ahora;
        elemTiempo.textContent = ahora - fechaInicio;
    };
}
let contarTiempo;
document.getElementById("iniciar-modulo").addEventListener("click", 
function(event){
    event.target.value = "Reiniciar módulo";
    document.getElementById("aviso-tiempo").textContent = "";
    contarTiempo = iniciarContador(document.getElementById("tiempo"),
        document.getElementById("fecha-inicio"),
        document.getElementById("fecha-ahora"));    
}, false);
document.getElementById("contar-tiempo").addEventListener("click", 
function(){
    if (contarTiempo) {
        contarTiempo();
    } else {
        document.getElementById("aviso-tiempo").textContent = 'El módulo no ha sido iniciado.';
    }
}, false);
    

Ejemplo: Devolviendo una función

Inicio:
Ahora:
Tiempo transcurrido: 0 ms
Este ejemplo usa ES6 y modo estricto. Puedes consultar el código JS original de este ejemplo.

Funciones auto-ejecutables (IIFE)

Vimos antes algunas situaciones que requieren el uso de una expresión de función. Otra situación especial para esas funciones son los IIFE (Inmediate Invoked Function Execution), conocida como función auto-ejecutable, que no es otra cosa que una función que se declara y se ejecuta inmediatamente. Esto ya lo comentamos en el apartado funciones auto-ejecutables de un tema sobre closures. Ahora veremos porque tiene que ser una expresión de función en lugar de una declaración de función.

Ya dijimos al inicio que el objetivo general de una función es agrupar un trozo de código para ejecutar una tarea repetidas veces, llamándola desde distintas partes del resto del código. Pero a veces necesitamos que se ejecute inmediatamente tras la declaración sin que nos haga falta usarla posteriormente. Podríamos usar una declaración de función y luego ejecutarla, pero la función seguiría existiendo.

function fun(){
    console.log(1);
};
fun(); // 1
console.log(typeof fun); // "function"
    

Por definición, la propia declaración de función no puede ejecutarse al mismo momento en que se declara, dándonos lo siguiente un error:

function fun(){
    console.log(1);
}(); // SyntaxError
    

Pero una expresion de función (nominada o no) si podría autoejecutarse tras la declaración:

let x = function fun(){
    console.log(1);
}();  // 1
console.log(typeof x); // "undefined"
    

Vea que ahora en x tenemos un tipo undefined porque la función no tiene un return y undefined es lo que devuelve la ejecución. Si quitamos los () del final entonces tendremos un tipo function en la variable que sería el resultado de la expresión de función:

let x = function fun(){
    console.log(1);
}; 
console.log(typeof x); // "function"
    

Ya vamos viendo que papel tan importante puede jugar un par de paréntesis. Pero hay más. Si rodeamos con paréntesis sólo la expresión de función, sin asignar a ninguna variable, veremos que la función se ejecuta también al momento de declararla, además de que ya no existirá nada en el exterior. Esto es precisamente lo que estamos buscando.

(function fun(){
    console.log(1);
}()); // 1
console.log(typeof fun); // "undefined"
    

Al rodear con paréntesis pasa de ser una declaración de función a una expresión de función nominada y por tanto si puede ejecutarse. Y esto son las sorpresas que JavaScript nos trae a veces, como unos simples paréntesis cambian todo. Intentaré explicar el motivo. Cuando JavaScript encuentra los paréntesis exteriores entiende que todo lo que hay en el interior es una expresión. Analiza la parte function fun(){ } como una expresión de función y no una declaración. Cuando a una referencia a función o expresión de función le sigue un grupo de paréntesis, JavaScript entiende que son los argumentos para una llamada a la función previa. Y la ejecuta en ese mismo momento.

La función anterior es nominada pero sólo a efectos del cuerpo interior de la misma, por lo que si no la vamos a referenciar internamente podemos convertirla en anónima:

(function (){
    console.log(1);
}()); // 1
    

Los paréntesis pueden utilizarse también de esta forma, rodeando sólo la función:

(function (){
    console.log(1);
})(); // 1
    

La forma de actuar es ligeramente diferente. Cuando JavaScript encuentra (function (){ }) entiende que el interior de los paréntesis exteriores es una expresión de función. Recuerda que los paréntesis, como operadores, se resuelven devolviendo el último elemento de la lista separada por comas, en este caso los parentésis devuelven la función (referencia el objeto de tipo function). Tras encontrar a continuación () al final de la sentencia entiende que son los argumentos para una llamada a la función anterior y la ejecuta.

Funciones auto-ejecutables para implementar datos encapsulados

En el tema de encapsulamiento con closures se comenta en mayor amplitud la utilidad de las funciones auto-ejecutables para realizar el encapsulamiento de datos y construir módulos en JavaScript. Aquí pondremos un ejemplo muy simple para entender este concepto:

let miModulo = (function(){
    //Estas serán variables privadas del módulo
    const nombreModulo = "Mi módulo";
    const version = "1.1.1"; 
    const fechaInicio = new Date();
    //Este es un método privado
    function obtenerFecha() {
        return `Ahora: ${new Date()}`;
    }
    //Devolvemos un objeto con variables públicas
    return {
        //Esto es una variable pública
        miVarPublica: "abc",
        //Esto es un método público con el que accedemos
        //a las variables privadas
        listarVariables: function(){
            return `<ul><li>Módulo: ${nombreModulo}</li>
                <li>Versión: ${version}</li>
                <li>Fecha Inicio: ${fechaInicio}</li>
                <li>Fecha Actual: ${obtenerFecha()}</li></ul>`;
        }
    };
})();
document.getElementById("ver-detalles").addEventListener("click",
function(){
    document.getElementById("mi-modulo-detalles").innerHTML = 
    `<div>Variables privadas accedidas con método público:` +
    `${miModulo.listarVariables()}</div>` +
    `<div>Variable pública: ${miModulo.miVarPublica}</div>`;
}, false);
    

Si ponemos ese código en el script principal de una página se ejecutará la función de forma inmediata, asignando el módulo a la variable miModulo. Las constantes y la función obtenerFecha() quedarán almacenadas en el alcance (closure) de esa función. Incluso cuando finalice la ejecución de la función esos datos seguirán almacenados y podremos acceder a ellos. Pero ese acceso estará limitado al interior de la función ya que esas variables y métodos privados no serán accesibles exteriormente.

Sólo podremos acceder a lo privado a través de los métodos públicos, para lo que devolvemos un objeto con propiedades y métodos públicos que conforman el módulo miModulo. Para probarlo adjudicamos un evento a un botón para ejecutar el método público miModulo.listarVariables() y también acceder a su variable pública miModulo.miVarPublica.

Ejemplo: Encapsulando datos para construir un módulo

El siguiente botón accede a las variables privadas que sólo son accesibles dentro del módulo. Con cada ejecución se actualiza la fecha actual.

Este ejemplo usa ES6 y modo estricto. Puedes consultar el código JS original de este ejemplo.

Funciones auto-ejecutables para iniciar propiedades

Otra utilidad de la funciones auto-ejecutables es cuando definimos un objeto con propiedades y hemos de asignar un valor inicial a una de ellas en el momento de la ejecución. En el siguiente ejemplo tenemos que darle valor a la propiedad titulo, tomándolo del elemento <title> de la página donde se ejecute el script.

//Ejemplo para iniciar propiedades de un objeto
let miObjeto = {
    nombre: "Mi Objeto",
    fechaCarga: new Date(),
    clave:  Math.round(Math.random()*10000, 0),
    //Iniciamos esta propiedad con una función auto-ejecutable
    titulo:  (function(){
        let title = "";
        let elemTitle = document.getElementsByTagName("title");
        if (elemTitle && (elemTitle.length > 0)) {
            title = elemTitle[0].textContent;
        }
        return title;
    })()
};
//Esto es para extraer las propiedades del objeto
document.getElementById("ver-objeto").addEventListener("click", 
    function(){
        let html = `<ul>`;
        for (let i in miObjeto){
            if (miObjeto.hasOwnProperty(i)){
                html += `<li>${i}: ${miObjeto[i]}</li>`;
            }                        
        }
        html += `</ul><div>Fecha actual : ${new Date()}</div>`;
        document.getElementById("contenido-objeto").innerHTML = html;
    }, false);
    

Ejemplo: Iniciar propiedades con una función auto-ejecutable

El siguiente botón nos verterá el contenido del objeto. Se agrega la fecha actual para observar cada ejecución, pero ese campo no forma parte del objeto.

Este ejemplo usa ES6 y modo estricto. Puedes consultar el código JS original de este ejemplo.

La aplicación parcial y el método bind()

Una función de aplicación parcial es una función que toma otra función con varios argumentos, fija algunos de ellos que se determinen previamente y devuelve otra función con el resto de argumentos no fijados. Supongamos un ejemplo muy simple donde tenemos que facturar ciertas unidades de un artículo, a un precio y descuento dados. La función podría ser como la siguiente:

function facturarArticulo(descuento, unidades, precio){
    return unidades * precio * (1-descuento/100);
}
    

Es posible que tengamos sólo tres tipos de descuentos. Para evitar errores al introducir un descuento inadecuado, podríamos crear otras tres funciones fijando los únicos descuentos admisibles.

function ceroPorCiento(unidades, precio){
    return facturarArticulo(0, unidades, precio);
}
function tresPorCiento(unidades, precio){
    return facturarArticulo(3, unidades, precio);
}
function cincoPorCiento(unidades, precio){
    return facturarArticulo(5, unidades, precio);
}
    

Lo que acabamos de hacer es fijar el primer argumento. Usando esas tres funciones aseguramos no aplicar un descuento que no sea uno de esos. Pero manejar esas tres funciones sería una solución particular para ese caso y nos interesa encontrar una solución general a este tipo de problemas. Fijar el primer argumento y ejecutar la función con los argumentos restantes es precisamente una aplicación parcial de la función facturarArticulo(). A continuación mostramos como se resuelve:

function aplicarParcial(funcion /*, arg1, arg2, ...*/) {
    //Pasamos a un array los argumentos implícitos arg1, arg2, ...
    let args = Array.prototype.slice.call(arguments, 1);
    //Devolvemos una nueva función
    return function() {
        //Esta función devuelve un VALOR ejecutando la función original
        //con los argumentos anteriores al principio y los nuevos a 
        //continuación
        return funcion.apply(null, 
            args.concat(Array.prototype.slice.call(arguments, 0)));
    };
}
//Ahora definimos nuevas funciones a partir de la anterior fijando
//el primer argumento para el descuento de la factura
let ceroPorCiento = aplicarParcial(facturarArticulo, 0);
let tresPorCiento = aplicarParcial(facturarArticulo, 3);
let cincoPorCiento = aplicarParcial(facturarArticulo, 5);
//Así aplicamos estas funciones sólo con 2 argumentos: unidades y precio
console.log(ceroPorCiento(1, 100)); // 100
console.log(tresPorCiento(1, 100)); // 97
console.log(cincoPorCiento(1, 100)); // 95
    

Antes de explicar como funciona aplicarParcial() recordemos que en arguments tenemos disponibles todos los argumentos de la función. De hecho aunque no estén explícitamente declarados podemos acceder a ellos. A veces los vemos en un comentario dentro de los paréntesis de cierre de los argumentos para evidenciar ese hecho.

Por otro lado con el método de Array slice(inicio, fin) podemos extraer una porción de un array, devolviéndonos otro array. Si fin se omite extraerá hasta el final.

También tenemos que conocer como funcionan call() y apply(), métodos de Function que nos permiten llamar a una función, cambiando el contexto de la referencia this y/o proveerla de argumentos para su ejecución. Asignado un nuevo this podemos ejecutar los métodos de un objeto en otro objeto que no posea ese método. Los dos métodos hacen lo mismo, sólo que a call() hay que pasarle los argumentos separados por comas, igual que con las funciones, mientras que para apply() le pasamos un array de argumentos.

Pero arguments no es un array. Se suele decir que es array-like porque dispone de la propiedad length y podemos acceder a sus posiciones como si fuera un array. Por eso usamos call() que aplica slice() sobre arguments para obtener el array de todos los argumentos menos el primero, que es la función que estamos pasando. Cuando en el ejemplo hagamos aplicarParcial(facturarArticulo, 3) tendremos en esa variable args el array [3], desechando el primer argumento, la función facturarArticulo.

Una aplicación parcial ha de devolver una función. Por eso le sigue un return function(){...};. Ahora sólo resta ejecutar la función parcialmente una vez hemos fijado el primer argumento. Usamos apply() para aplicar un array de argumentos a la función que vamos a parcializar. Usando el método concat() concatenemos el array anterior args que había quedado en [3] con el resultante de los argumentos que suplirá la función. Sí por ejemplo aplicamos tresPorCiento(1, 100) nos resultará en el array [1, 100], que concatenado al anterior tendremos [3, 1, 100]. Haciendo apply() sobre la función parcialmente aplicada al final se ejecutará facturarArticulo(3, 1, 100).

La función de aplicación parcial que hemos visto nos serviría para resolver todos los problemas donde hubiera que fijar los primeros argumentos de la izquierda y ejecutar el resto (fijar por la izquierda). Otra posibilidad sería fijar el último y ejecutar los anteriores (fijar por la derecha). O con caracter general fijar cualquier número de argumentos en cualquier posición y ejecutar el resto. Para estas dos opciones tendríamos que modificar la función aplicarParcial() para que maneje los argumentos de forma adecuada.

Cuando el problema es sólo fijar los argumentos de la izquierda podemos usar el método bind() de una función para conseguir la aplicación parcial. La sintaxis del método es f.bind(nuevoThis[, arg1[, arg2[, ...]]]), siendo f la función sobre la que se aplica. Si no va a producirse un cambio de contexto para this podemos poner nulo ese primer argumento. Lo que obtenemos es una nueva función donde se fijan los argumentos que se pasen desde la izquierda. Al aplicarlo a nuestra función facturarArticulo(descuento, unidades, precio) fijamos sólo el primer argumento haciendo facturarArticulo.bind(null, 0) para el caso del cero por ciento de descuento. Eso nos devolvería una nueva función g(unidades, precio) que será la aplicación parcial de facturarArticulo una vez fijado su primer argumento con valor cero.

let ceroPorCiento = facturarArticulo.bind(null, 0);
let tresPorCiento = facturarArticulo.bind(null, 3);
let cincoPorCiento = facturarArticulo.bind(null, 5);
//Así aplicamos estas funciones sólo con 2 argumentos: unidades y precio
console.log(ceroPorCiento(1, 100)); // 100
console.log(tresPorCiento(1, 100)); // 97
console.log(cincoPorCiento(1, 100)); // 95
    

El siguiente ejemplo pone en práctica lo anterior:

Ejemplo: Aplicación parcial

Facturar:
Importe:
Este ejemplo usa ES6 y modo estricto. Puedes consultar el código JS original de este ejemplo.

Currying

Aplicar currying a una función consiste en transformar una función con N argumentos de tal forma que se pueda ejecutar como una secuencia de N funciones, cada una con un único argumento. La función function f(x, y){return x+y} se ejecutaría como f(1, 2) devolviéndonos el resultado 3. Si le aplicamos esta técnica obtendríamos otra función g tal que para ejecutarla haríamos g(1)(2). Veáse que para que esta expresión nos devuelva igual resultado significa que g(1) ha de devolver una función, digamos que sería h, que aplicado sobre el siguiente y último argumento como h(2) nos devolvería el resultado final 3.

Haremos un ejemplo para verlo en funcionamiento. El código es el siguiente:

function curry(funcion) {
    return (function recursiva(anteriores) {
        return function(arg) {
            let actuales = anteriores.concat(arg);
            if (actuales.length < funcion.length) {
                return recursiva(actuales);
            } else {
                return funcion.apply(null, actuales);
            }
        };
    })([]);
}
//El curry anterior lo usaremos para la siguiente 
//función que aplica estilo CSS a un elemento
function aplicarEstilo(elemento, propiedad, valor){
    elemento.textContent = Date();
    elemento.style[propiedad] = valor;
    return elemento.textContent;
}
//Haciendo currying a la función para fijar el primer 
//argumento que apunta al elemento
let estilar = curry(aplicarEstilo);
let estilarDiv1 = estilar(document.getElementById("DIV1"));
//Y entonces podemos usar esa función para aplicar estilos
estilarDiv1("color")("red");
estilarDiv1("border")("blue solid 1px");
estilarDiv1("background")("pink");
    

Al final de este apartado encontrará el ejemplo que aplica todo esto. En los siguientes párrafos trataré de explicar como funciona curry(). Pero antes creo que merece decir que ese código, con cambios no significativos, lo consulté en la página Partial Application in JavaScript de Ben Alman. Ahí explica también lo de la aplicación parcial. Puedes ver la definición matemática Wikipedia Currying.

La función curry() tiene como argumento a la función sobre la que se aplicará. En nuestro ejemplo la usaremos con aplicarEstilo() que aplica estilo CSS a un elemento. En curry() estamos devolviendo el resultado de una función auto-ejecutable, pasando como argumento un array vacío. Esto se hace para no tener que transformar en un array los argumentos con slice(), como hacíamos en el caso de la aplicación parcial.

Observe que es una función anónima con nombre porque es recursiva. Dígamos que es recursiva de un sólo paso, pues con la primera llamada a sí misma sale del recursivo con el return, devolviendo una función.

La primera instrucción es hacer el currying de la función:

let estilar = curry(aplicarEstilo);

El argumento anteriores será entonces un array vacío. La devolución que asignaremos a estilar es la función function(arg){...}, siempre con un único argumento, función que a partir de aquí es la que entra en juego. Pero no olvidemos que esa función está en un contexto y por el efecto closure, incluida dentro de la función recursiva y, a su vez, en el contexto de la función curry(). Así la función interior tendrá acceso a recursiva(), a su argumento anteriores y también al argumento funcion de la función curry().

La siguiente instrucción consigue fijar el primer argumento:

let estilarDiv1 = estilar(document.getElementById("DIV1"));

En esta ejecución tendremos el elemento en el argumento arg, mientras que anteriores del closure quedó en un array vacío. Los concatenamos y tendremos en actuales un array con la referencia al elemento [element]. Como la longitud de actuales es uno, menor que la de la función aplicarEstilo() que tiene tres argumentos, salimos por la opción que vuelve a ejecutar la recursiva devolviendo una función con un único argumento arg para ejecutar el posterior en la secuencia. En cada ejecución de la recursiva traemos actuales al argumento anteriores. Digamos que así vamos acumulando los valores de los argumentos entre llamadas, teniendo ahora el primero con la referencia al elemento.

La siguiente instrucción aplicará el resto de argumentos:

estilarDiv1("color")("red");

Realmente se ejecuta en dos pasos, pues también podríamos codificarlo con

let g = estilarDiv1("color");

y a continuación la siguiente función:

let h = g("red");

Al obtener g() habremos incorporado al array anteriores un nuevo valor de argumento, quedando en [element, "color"]. Aún actuales.length es menor que funcion.length y, por tanto, devuelve otra vez una función.

En el siguiente y último paso obtenemos la función h(), quedando el array actuales con [element, "color", "red"]. Como ahora actuales.length == funcion.length en lugar de devolver una función llamando al recursivo, usamos apply() para ejecutar la función original, es decir, aplicarEstilo(elemento, propiedad, valor), usando como argumentos el array actuales.

Ejemplo: Currying

Este es el código que aplicará el estilo:

let estilarDiv1 = estilar(document.getElementById("DIV1"));
estilarDiv1("color")("red");
        

Los tipos devueltos y argumentos en cada llamada de la secuencia de funciones son:

FunciónTipo devueltoArgumentos
estilarDiv1
estilarDiv1("")?
estilarDiv1("")?

A continuación se muestra el elemento DIV sobre el que se aplicará el estilo:

Elemento con ID = "DIV1"
Este ejemplo usa ES6 y modo estricto. Puedes consultar el código JS original de este ejemplo.

En estos últimos apartados he tratado de exponer ejemplos donde entran en juego las funciones. Son sólo una muestra de la potencialidad de las mismas para conseguir técnicas de gran potencia. Por lo tanto entender como trabajan las funciones en JavaScript es primordial para sacarle el mayor provecho posible a este lenguaje de programación. En el siguiente tema hablaremos de las propiedades y métodos de las funciones.