JASoft.org

El blog de José Manuel Alarcón Aguín. Programación web y mucho más...

MENÚ - JASoft: JM Alarcón

Escribiendo código JavaScript limpio: funciones anónimas auto-ejecutables

Funcion-Anonima-JavaScriptUn patrón muy utilizado a la hora de escribir código JavaScript son las funciones anónimas auto-ejecutables.

La idea es la siguiente: aprovechar las propiedades de ámbito de las variables de JavaScript y el uso de clausuras para escribir código más limpio que no interfiera con otro código JavaScript que pudiera haber en la página. De hecho es la técnica que utilizan muchas de las bibliotecas importantes para inicializarse.

Por ejemplo, consideremos el siguiente fragmento de código JavaScript:

var v1 = 0;

function miFunc1(){
	v1 = 5;
	alert(v1);
}

function miFunc2(){
	v1 = 10;
	alert(v1);
}

En este código tan sencillo estamos definiendo una variable y dos funciones. Dado que desde ambas funciones debemos poder acceder a la misma variable común "x", la declaramos de manera global, siendo accesible desde toda la página. Este código, si bien funcionará correctamente, tiene varios problemas, entre los que cabe destacar los dos siguientes:

  • La variable, siendo global, será accesible desde cualquier función de la página, no solo desde las dos que la necesitan y tampoco solo desde nuestras funciones. Otras funciones de otros scripts que incluyamos en la página también tendrán acceso.
  • Por mucho cuidado que tengamos, si los nombres de la variable o de las funciones coinciden con los de otras variables o funciones globales definidas en otro script, tendremos un conflicto y se producirán errores. Estamos polucionando innecesariamente el objeto global.

Lo que buscamos es la manera de poder definir funciones que compartan información entre sí, la almacenen para ejecución diferida y que no polucionen el código global de la página.

Obviamente podríamos definir una clase, la cual podríamos instanciar luego para ser utilizada. La clase nos permitiría definir miembros privados (aunque en JavaScript esto no es tan directo como en otros lenguajes) así como propiedades y métodos específicos de la misma. Posteriormente la instanciamos y hacemos uso de ella.

Eso puede estar bien en ocasiones, pero si el código es de un solo uso (como por ejemplo una inicialización) o son funciones que queremos usar de modo estático (sin instanciar nada explícitamente) estamos matando moscas a cañonazos. Y más teniendo en cuenta que en JavaScript la orientación a objetos no es tan sencilla y directa como en otros lenguajes.

Así que vamos a hacer un pequeño cambio en el código anterior y lo envolveremos con una cierta estructura, como se muestra a continuación:

(function (){
	var v1 = 0;

	function miFunc1(){
		v1 = 5;
		alert(v1);
	};

	function miFunc2(){
		v1 = 10;
		alert(v1);
	};
})();

¿Cómo? ¿Qué hemos hecho aquí? Vamos a verlo con calma.

El código es casi idéntico al anterior, pero en este caso lo hemos rodeado con unas llaves y una palabra clave function.

Nota: Fíjate además que para evitar problemas para distinguir cada instrucción de código, a las funciones originales se les añade un punto y coma al final, después de la última llave. No es indispensable pero ayuda mucho con la legibilidad del código y evita posibles problemas de interpretación.

Con esta sintaxis estamos definiendo una función anónima (no estamos indicando nombre alguna para ésta). Es como la definición de una función cualquiera, solo que sin nombre:

function () {....}

Además a esta función la rodeamos de paréntesis, con lo cual obtenemos una referencia a la misma (más sobre esto en breve), y posteriormente le colocamos dos paréntesis más, así:

(function (){....})();

¿Qué estamos haciendo? Los dos paréntesis finales se utilizan para llamar a la función anónima. En JavaScript una función se ejecuta cuando la llamamos usando los paréntesis. Sin paréntesis se obtiene una referencia a la misma. Por ejemplo:

var f1, f2;
f1 = miFuncion;
f2 = miFuncion();

Tras ejecutar este código, la variable f1 tendrá una referencia a la función miFuncion. La variable f2 sin embargo almacenará el valor devuelto por la función miFuncion tras su ejecución. La diferencia está en los paréntesis. De hecho podríamos llamar a la misma función escribiendo simplemente f1(); ya que f1 contiene una referencia a la función como he dicho.

Por lo tanto lo que hacemos con la estructura anterior es definir una función anónima y al mismo tiempo llamarla.

¡Con eso se crea, se ejecuta y desaparece!

De esta manera conseguimos que todo lo que hay dentro esté aislado del resto del código de la página. Mantiene su propia identidad pero no interfiere. En un ejemplo real, aparte de realizar posibles tareas de inicialización dentro, crearíamos probablemente algunos objetos especializados que expondríamos al exterior a través del objeto global, creando así clases de una sola instancia (Singleton), que además tendrían acceso a ciertos datos internos los cuales jamás interferirían con el resto del código tampoco.

¿Por qué hay que rodearla con paréntesis? ¿Funcionaría sin eso?

Vamos a probarlo: quitémosle los paréntesis que rodean a la definición de función y vamos a ver que pasa... Esto es lo que muestra la consola de los principales navegadores:

Error_Falta_Parentesis_Func_Anonima

Es decir, que todos generan un error de sintaxis, aunque el mensaje que devuelven es diferente y en general no resulta de mucha ayuda.

El problema estriba en que el intérprete del lenguaje no sabe cómo interpretar el código que tenemos escrito, ya que después de function siempre va el nombre de la función, salvo que esta sea anónima y por lo tanto la estemos asignando a alguna variable o la evaluemos. Por eso, si la rodeamos de paréntesis, estamos forzando a que se evalúe.

Pero esta no es la única forma de hacerlo, y de hecho verás por ahí código similar que en lugar de paréntesis utiliza cualquier otro operador que actúe sobre la función, ya que así se obliga a ejecutarla y obtener su resultado, por ejemplo:

!function (){....}();

o bien:

+function (){....}();

e incluso cosas mucho más peregrinas e innecesarias, como:

!+-~function (){....}();

que también funciona, pero son una tontería desde mi punto de vista.

A mi personalmente me parece que la forma más apropiada es la primera que he ilustrado, con los paréntesis, por que es clara, directa y no fuerza realizar operación adicional alguna, pero conviene reconocer otras formas por si las vemos por ahí.

Paso de parámetros de inicialización

Otra utilidad de uso muy común es pasarle parámetros de inicialización a la función, de modo que podamos referenciarlos desde la función anónima, modificarlos y mantener sus valores originales si son tipos por valor. Este patrón es muy común en algunas bibliotecas y de hecho jQuery lo ha utilizado durante muchos años para inicializarse:

(function (w){
	function miFunc1(){
		alert(w.document.title);
	};

	miFunc1();
})(window);

De este modo, por ejemplo, estamos pasando el objeto global como parámetro inicial y lo usamos desde dentro con un alias "w" que perdurará cuando llamemos a las diferentes funciones internas si las exponemos hacia el exterior. Además, pasando el objeto window de esta manera y almacenándolo en "w" estamos también ganando rendimiento. El motivo es que al estar definido en una variable local a las funciones que definamos dentro de la función anónima, el intérprete las encuentra antes y es mas eficiente en su uso. De hecho es muy habitual pasar como parámetro de inicialización otros objetos comunes como document, por ejemplo:

(function (w, d, undefined){
	function miFunc1(){
		alert(d.title);
	};

	miFunc1();
})(window, document);

Fíjate en un detalle importante en este código anterior. ¿Para qué es el parámetro undefined de la función anónima que además no se usa?

En JavaScript nada nos impide escribir código como el siguiente:

undefined = 1;

aunque undefined sea una palabra clave. Lo que hacemos es cambiar la definición de undefined y esto obviamente afecta al resto del código que estamos utilizando. ¿Por qué iba a hacer alguien esto? No lo sé, pero lo cierto es que ocurre (muchas veces incluso por error al hacer comparaciones erróneamente usando = (asignando) en lugar de == para comparar).

En las versiones modernas de los navegadores, que implementan ECMAScript 5 o posterior, la instrucción anterior no tiene efecto. Pero en navegadores antiguos como Internet Explorer 8 o anterior, sí que funciona y puede causar estragos (pruébalo con el modo de compatibilidad de las herramientas del desarrollador y verás que así es).

Por ello incluir un tercer parámetro de nombre undefined, el cual no se le pasa a la llamada de la función anónima (fíjate en que solo se le pasan 2), lo que hace en la práctica es definir una variable local /parámetro de la función que se llama undefined y nos aseguramos de que tendrá el valor de undefined siempre (salvo que se se haya redefinido antes, claro). Por eso mismo en código antiguo sobre todo lo verás utilizado así en muchas ocasiones y era considerado una buena práctica.

En resumen

En definitiva, con las funciones anónimas auto-ejecutadas conseguimos ejecutar código y definir funciones y propiedades aisladas del resto de los elementos de script de la página, evitando posibles interferencias. También nos permite entre otras cosas precargar valores para ejecución tardía, aislar variables y métodos para uso interno de nuestro código o definir bien las variables de contexto (this) sobre las que se va a ejecutar el mismo.

Además es la base de algunos patrones de diseño en JavaScript, siendo el más común el patrón Módulo, aunque también de otros como los patrones Singleton o Factoría.

En futuros artículos aquí o en campusMVP, usaré este patrón para generar el código de utilidades y le veremos más utilidad. En cualquier caso lo verás utilizado constantemente por ahí, así que es recomendable que lo conozcas.

Aprende mucho más con mi curso: Técnicas avanzadas para programadores JavaScript

José Manuel Alarcón José Manuel Alarcón
Fundador de campusMVP.es, el proyecto de referencia en formación on-line para programadores en lengua española. Autor de varios libros y cientos de artículos. Galardonado como MVP de Microsoft desde 2004. Gallego de Vigo, amante de la ciencia y la tecnología, la música y la lectura. Ayudando a la gente en Internet desde 1996.
Descarga GRATIS mi último libro (no técnico): "Tres Monos, Diez Minutos".
Banner

Comentarios (7) -

Hola, antes que nada felicitarte por tu excelente tutorial, estoy empezando en el mundo de JS y me has quitado algunas dudas que tenía.
Muchas gracias por compartir!

Responder

Spain José Manuel Alarcón

Gracias. Me alegro.

Si quieres aprender a fondo de verdad JavaScript no te olvides de este curso que he creado:

www.campusmvp.es/.../...adores-JavaScript_206.aspx

Además estaré todo el rato en la plataforma disponible para resolver tus dudas directas.

Saludos.

Responder

Gracias por el tutorial !

Responder

Latin America Gerardo Cabrera

Buenas tardes.

Estoy utilizando este codigo javascript:

classDescuentos = function () {
};

classDescuentos.prototype = {
var cellsEditar = function (row, column, columnfield, value, defaulthtml, columnproperties) {
            var data = grid.jqxGrid('getrowdata', row);
            var style = "";

            if (data.editar == 't' && data.apr_ger == 'f') {
                style = "display:block";
            } else {
                style = "display:none";
            }

            var html = "";
            var activarBotEnv = actBtn;
            html = "<div id='activarEdicion_" + row + "' style='width:99%;text-align:center;" + style + "' ><a href='#' id='editarHora' name='editarHora' title='Editar Total horas descontar' onclick=classDescuentos.prototype.editarHora(" + row + ");><img style='padding-bottom: 0px' height='17px' width='17px' src=../../../media/img/edit.png></a></div>";
            return html;
        };

editarHora: function (row) {
        var grid = $(this.gridData);
        grid.jqxGrid('setcolumnproperty', 'des_hor_ger_format', 'editable', true);
        grid.jqxGrid('begincelledit', row, "des_hor_ger_format");

        grid.on('cellendedit', function () {
            grid.jqxGrid('setcolumnproperty', 'des_hor_ger_format', 'editable', false);
        });
    }
};

Pero cuando llamo a la función editarHora en el onclick de la etiqueta a no ejecuta lo que quiero. Qué estoy haciendo mal que no funciona?

Responder

Spain José M. Alarcón

Hola Gerardo:

Esto no tiene mucho que ver con el tema del post que son las funciones anónimas, sino más bien con conceptos de programación orientada a objetos con JavaScript, ámbitos, clausuras, etc... que son cuestiones muy amplias que dan para mucho más que un comentario en un blog e incluso que un post aunque sea largo.

En ese código hay unas cuantas cosas que no están muy bien. Para empezar la mezcla de lógica e interfaz que existe que hará dificilísimo depurarlo y mantenerlo en el futuro. Por otro lado:

1.- No hace falta que declares la clase como una función vacía. Con que le asignes un objeto vacío con {}, suficiente y más claro en este caso.

2.- Le estás asociando al prototipo una clase pero ésta está mal escrita y produce un error de sintaxis ¿no lo has visto en el depurador del navegador que uses (F12)?. No puedes definir una variable directamente dentro del par de llaves de la clase como estás haciendo tú. Además esa función/variable 'cellsEditar' no la usas en ningún lado ni estaría accesible para nadie en el exterior de tu clase aunque estuviese bien definida.

3.- Los prototipos no se usan directamente, salvo en contadas ocasiones. Aunque este código te funcionara deberías llamar a la función 'editarHora' directamente desde la clase 'classDescuentos', no desde su prototipo. Todo el concepto de herencia de prototipos de basa en este uso, y la función es compartida entre todas las instancias de la clase. Si lo que quieres es una función estática esa no es la mejor manera de hacerla y no te hacen falta prototipos para nada.

4.- Aprovechando clausuras lo suyo sería que hicieses esa función 'editarHora' en el prototipo pero utilizando ya la propia referencia a la clase que la esté usando, sin necesidad de que le pases ningún parámetro. Es decir, la propia función sabría sobre qué celda está actuando sin necesidad de que le pases un argumento indicándoselo como haces ahora (le pasas el parámetro 'row', que no sería necesario haciéndolo bien orientado a objetos).

Te recomiendo encarecidamente que te animes a hacer mi curso de programación de Técnicas Avanzadas para programadores JavaScript:

www.campusmvp.es/.../...adores-JavaScript_206.aspx

en el se tratan muy a fondo multitud de conceptos similares a este que te quedarán claros para evitar este tipo de problemas en el futuro.

Saludos.

Responder

Latin America Gerardo Cabrera

Muchas gracias por tu muy detallada respuesta. También creo que fue error mío no colocar el código mas específico, lo que coloque es solo parte del código y era solo lo que quería resolver. De nuevo gracias por la ayuda y me fijaré en lo que me comentaste y buscaré lecturas y tutoriales porque hacer tu curso esta complicado por cuestiones monetarias.

Saludos.

Responder

Sergio Tuxsoft

Excelente información sobre JavaScript, me ayudo bastante para comprender, ya que apenas me estoy iniciando como desarrollador web, todo es de suma relevancia para poder trabajar ordenadamente.
Saludos desde mexico.

Responder

Agregar comentario