Contenido

Scripts que se auto programan en javascript

21 ene

+ 16

Hace tiempo que javascript me tiene enamorado y poco a poco voy descubriendo nuevas propiedades y nuevas técnicas de programación que no podríamos tener en otros lenguajes.

Una curiosidad de javascript es la de poder igualar variables a funciones:

function nombre(){ alert("Hola"); }

//
 var variable = nombre();
// -> Muestra un alert()

 console.log(variable.toString());
// TypeError: variable is undefined

// igualamos
var foo = nombre;

console.log(foo.toString());
//function nombre() { alert("Hola"); }

Como vemos en el ejemplo anterior, si deseamos igualar una variable a una función deberemos hacer sin añadir los paréntesis que se añaden habitualmente a las funciones, añadir los paréntesis implica la ejecución de la función. De esta forma, podemos aprovecharnos de dicha propiedad para hacer cosas interesantes:

var $$$ = function(){
	if (document.getElementsByClassName){
		return function(klass){
			return document.getElementsByClassName(klass);
		}
	} else {
		return function(klass){
			var result = new Array();
			var reg = new RegExp( "(?:^|\\s)" + klass.replace( /\./g, "\\s*" ) + "(?:\\s|$)" );
			var es = (document.all) ? document.all : document.getElementsByTagName("*");
			for( var index = 0, e; e = es[ index++ ]; ) {
				if( reg.test( e.className ) ) {
					result[ result.length ] = e;
				}
			}
			return result;
		}
	}
}()

Este ejemplo nos sirve para ilustrar lo que intento explicar en este artículo. Si nos fijamos en el código, está condicionado por la existencia del método getElementsByClassName del objeto document, en caso de existir devolvemos una función que la usa para obtener los elementos con la clase solicitada.

En caso de no existir dicho método, se encarga de devolver una funcionalidad desarrollada usando getElementsByTagName con la que obtenemos el mismo resultado pero en navegadores donde getElementsByClassName no está disponible.

Al final del código (y en negrita) vemos los famosos paréntesis de los que hemos hablado antes. En este caso, ejecutará dicho código una vez pase por él, dejándonos la variable $$$ cargada con el método que más le convenga dependiendo de la existencia del método getElementsByClassName.

Hagamos unas pruebas :D

var $$$ = function(){
	if (document.getElementsByClassName){
		return function(klass){
			return document.getElementsByClassName(klass);
		}
	} else {
		return function(klass){
			var result = new Array();
			var reg = new RegExp( "(?:^|\\s)" + klass.replace( /\./g, "\\s*" ) + "(?:\\s|$)" );
			var es = (document.all) ? document.all : document.getElementsByTagName("*");
			for( var index = 0, e; e = es[ index++ ]; ) {
				if( reg.test( e.className ) ) {
					result[ result.length ] = e;
				}
			}
			return result;
		}
	}
}()

// Con getElementsByClassName
$$$.toString()
// --> "function (klass) { return document.getElementsByClassName(klass); }"

$$$("framework")
// --> [th.framework, th.framework, th.framework, th.framework, th.framework, th.framework]

//Sin getElementsByClassName
$$$.toString()
// --> "function (klass) { var result = new Array; var reg = new RegExp("(?:^|\\s)" + klass.replace(/\./g, "\\s*") + "(?:\\s|$)"); var es = document.all ? document.all : document.getElementsByTagName("*"); for (var index = 0, e; e = es[index++];) { if (reg.test(e.className)) { result[result.length] = e; } } return result; }"

$$$("framework")
// --> [th.framework, th.framework, th.framework, th.framework, th.framework, th.framework]

Ventajas

Al ser ejecutado al cargar el script tenemos una ventaja a la hora de ejecutar dicho método, no tendremos que realizar una comprobación de la función para elegir un camino u otro, la función ya estará cargada con el camino correcto que debe usar, en el caso del elemento el getElementsByClassName.

Desventajas

Perdemos unas milésimas de segundo en la carga del fichero, esto es debido a que ha de compilar el código y ejecutar la función, en este caso el tiempo no es significativo, pero en scripts más pesados y/o complejos puede llegar a notarse dicho tiempo, dependiendo el uso de la funcionalidad nos puede ser interesante usarlo o no.

¿Alguien sabe que nombre recibe esta técnica? Llevo tiempo usándola, pero nunca he sabido el nombre que recibe.

Actualización

Me he olvidado de algo que podría ser interesante. Resulta que además de esta funcionalidad, disponemos de la posibilidad de pasar una variable a la función y usarla dentro de la función que estamos auto generando.

var foo = true; // o false;

var func = function(bla){
	if (bla){
		return function(str){
			console.log("USAMOS CONSOLE: " + str);
		}
	} else {
		return function(str){
			alert("USAMOS ALERT: " +str);
		}
	}
}(foo)

En el ejemplo nos encontramos la declaración de la función que recibe un parámetro llamado bla, parámetro que pasamos en la última línea, entre los parámetros usando la variable foo. Dentro podremos usar bla que es el parámetro que estamos recibiendo dentro. Una curiosa forma de auto programar nuestras aplicaciones :D

  • Oye genial!, buen artículo, gracias por comparti.

  • Es una memoización arropada por un módulo innecesario: no necesitas esa función anonima fugaz para dar ámbito ya que no se hay ninguna variable local que no pertenezca a función.

    Bastaría con esto:

    
    var $$$ = document.getElementsByClassName ||
    function(klass){
      var result = new Array();
      var reg = new RegExp( "(?:^|\\s)" + klass.replace( /\./g, "\\s*" ) + "(?:\\s|$)" );
      var es = (document.all) ? document.all :  document.getElementsByTagName("*");
     for( var index = 0, e; e = es[ index++ ]; ) {
        if( reg.test( e.className ) ) {
           result[ result.length ] = e;
        }
     }
     return result;
    };
    
  • De esa manera evitamos la clausura y su correspondiente objeto de activación (que en ese caso estaría vacío).

    Un saludo.

  • Muy bueno el artículo, ayuda a comprender mejor como trabajan los frameworks javascript y como crean las distintas funcionalidades que nos ofrecen. Una pregunta, que hace esta línea exactamente?

    var reg = new RegExp( "(?:^|\\s)" + klass.replace( /\./g, "\\s*" ) + "(?:\\s|$)" );

    entiendo que es una expresión regular, pero no entiendo los símbolos usados.

  • ^ representa principio de expresión

    \s representa espacio

    ^|\s representa principio de línea o espacio

    Como la concatenación tiene preferencia frente a la disyunción necesitaremos agrupar esa subexpresión mediante ‘paréntesis’.
    Para indicar paréntesis se utiliza como apertura (?: y como cierre ):

    (?:^|\s) 

    Si abres con un paréntesis simple hace lo mismo pero además se memoriza lo encontrado.

    Supongo que sabiendo que $ significa final de expresión entenderás el significado de (?:\s|$)

  • @Jose: Exacto, pero para ilustrar el ejemplo he pensado que quedaría más claro hacerlo todo detallado.

    El ejemplo quizás no sea un buen ejemplo, pero no tenía otro código a mano :D
    @Raúl Negueruela: Jose, lo ha clavado.

    Simplemente lo que hace es asegurarme que encuentra el className entre la/s clases del elemento.

  • Vaya Andrés, he de confesar que lei el artículo demasiado rápido: acabo de darme cuenta de que no hablas de la ‘memoization’, sino de la definición y ejecución inmediata de lambdas (funciones anonimas) :)

    Un comentario sobre cuestiones estilísticas. Es recomendable colocar unos parentesís que envuelvan la definición de la función anonima en el caso de que vaya a ejecutarse inmediatamente. Es decir, en lugar de escribir:

    
    function(pars){body}(args)
    

    mucha gente escribe

    
    (function(pars){body})(args)
    

    La semántica es la misma y ayuda un poco a distinguir estos casos de definición-ejecución inmediata de aquellos en los que sólo definimos la función.

  • Ha este patrón también he visto que lo llaman “lazy function”.

  • En caso de que el tiempo de carga sea importante siempre se puede diferir la definición de la función a la primera vez que se utilice.

    $$$ = function(){
       $$$ = //nueva definición de la función costosa en tiempo
       $$$();
    }

    eso sí, en este caso, dentro de la función conocemos el nombre de la variable que se está definiendo.

  • @Jose: Exacto, pero por lo mismo que en el caso anterior, me parece más claro para los usuarios menos familiarizados con esta programación.

    Gracias.

  • @monkeydeveloper: No creo que llegue a ser metaprogramación, ya que él no se auto programa realmente simplemente sería más bien una “Metadefinición” ya que se auto define dependiendo de algunos factores.

  • @aNieto2k Con respecto al primer ejemplo, quiero recordar que se puede acceder a una función o variable a través de window[‘variable_funcion’].
    Ejemplos:

    window['jQuery'].toString();
    console.log( window['_uacct'] );
    console.log( window['$'].toString() );
    alert( window['alert'].toString() );
    

Comentar

#

Me reservo el derecho de eliminar y/o modificar los comentarios que contengan lenguaje inapropiado, spam u otras conductas no apropiadas en una comunidad civilizada. Si tu comentario no aparece, puede ser que akismet lo haya capturado, cada día lo reviso y lo coloco en su lugar. Siento las molestias.