2011/05/05

Optimización de javascript (XI). Ámbitos de ejecución

Para no hacer muy densa la entrada de ayer, la he dividido en dos partes.

Ésta sí que se refiere a optimización de javascript, ya que posiblemente, la creación de un ámbito de ejecución para nuestra aplicación sea la característica que más va a influir en la ejecución de nuestro javascript si éste es medianamente pesado. Además de las ventajas de tiempo de ejecución por reducción del ámbito, de regalo se tiene una aproximación a programación orientada a objetos con javascript que permite encapsular funcionalidades.

Un auténtico experto en el uso de ámbito para aplicaciones en tiempo real (demos gráficas) es el creador del sitio dhteumeuleu, que mediante una base de aplicación sencilla y un timer, hacer arte.

El esqueleto básico de nuestra aplicación 'App' debe ser el siguiente:
// Esqueleto de la clase App
var App = function() {
    /////////////////////////////////////////////////////////////
    // MIEMBROS PÚBLICOS DE LA CLASE
    /////////////////////////////////////////////////////////////
    // Objeto donde almacenaremos las propiedades públicas de la clase
    // Se accede a ellas mediante app.self.propx
    this.self = {
        prop1: valor,
        prop2: valor,
        // ...
    };

    // Otra manera de definir miembros públicos de la clase es mediante this
    this.prop = valor;
    this.funcionPublica = function (params) { /* código */ };

    /////////////////////////////////////////////////////////////
    // MIEMBROS PRIVADOS DE LA CLASE
    /////////////////////////////////////////////////////////////
    // ----- crossbrowsers addEvent ----- 
    function addEvent (o, e, f) { 
        if (window.addEventListener) o.addEventListener(e, f, false); 
        else if (window.attachEvent) r = o.attachEvent('on' + e, f); 
    } 

    // Variables privadas de la clase
    var _proppriv1 = valor;
    var _proppriv2 = valor;

    // Funciones privadas de la clase
    var func1 = function (params) { /* lo que sea */ };
    function func2(params) { /* lo que sea */ } // También se puede hacer así

    /////////////////////////////////////////////////////////////
    // CLASES
    /////////////////////////////////////////////////////////////
    // Además del resto de miembros privados y públicos, 
    // se pueden definir nuevas clases dentro de nuestra clase que se 
    // podrán usar dentro del objeto (y no fuera).
    // Evidentemente todos los miembros de nuestra clase son 
    // visibles para la clase "hija"
    var Coordenada = function (x, y) { 
        this.x = x; 
        this.y = y; 
    }; 

    /////////////////////////////////////////////////////////////
    // CONSTRUCTOR
    /////////////////////////////////////////////////////////////
    var init = function (params) {
        // Función interna del constructor
        // Llamada desde la función de instanciación
    };

    // Devuelve la función de instanciación de la clase
    // En este caso se vincula a un evento para que la ejecución no sea bloqueante
    return {
        init : function (params) { 
            addEvent(window, 'load', function () { 
                init(params); // Llama a la función de construcción
            }
        };
}();

/////////////////////////////////////////////////////////////
// FUNCIONES PÚBLICAS DE LA CLASE
// Se hace mediante el prototype por mayor velocidad de 
// ejecución a costa de algo más de memoria usada.
App.prototype.func1 = function( params ) { /* ... código ... */ };
App.prototype.func2 = function( params ) { /* ... código ... */ };
// ...
App.prototype.funcn = function( params ) { /* ... código ... */ };

Cuando se esté ejecutando código de una de las funciones de la clase, para acceder a la "instancia" (los valores de la instanciación del objeto) basta con hacer la llamada mediante
self.propx
ya que self está dentro del ámbito de la aplicación o mediante un simple
propy
en el caso de que se haya definido de la segunda manera, eso sí, no nos beneficiamos de la reducción del ámbito de búsqueda que nos da definirla dentro de "self".

Para crear la instancia de la aplicación se hace de la manera siguiente
<script>var miApp1 = app.init( { prop: valor, prop: valor, ... } );</script>
Si no quisiéramos usar una función "init" para instanciar la clase, se podría quitar toda la parte del constructor y se definiría la clase como
// Esqueleto de la clase app
var App2 = function( params ) {

    // ... 
    // El resto es igual, pero para acceder a los parámetros de instanciación
    // habría que hacer params.prop, con param en formato de objeto 
    // ...

    // ... Inicialización ...

    return self; // Dentro vamos a definir los miembros públicos además 
                 // de los definidos mediante this.xxx = yyy;
    return this; // Si no se quiere usar el objeto "self" siempre se 
                 // puede devolver this
}();

// Se llama mediante
var miApp2 = App2( { prop: valor, ... } );

En la llamada, se crea la instancia de la clase de la aplicación pasando como parámetro un objeto con los valores de la instanciación.

Si en algún momento ya no nos hacen falta propiedades que tenga definidas (ya sean variables o funciones incluso) se pueden borrar mediantedelete miApp.prop;
Os recuerdo que además, se pueden eleiminar elementos del DOM que ya no nos hagan falta para liberar memoria mediante
delete el.parentNode.removeChild(el);
Como extra, podemos aprovechar además para aplicar técnicas de "memoization" que no es más que cachear resultados de operaciones costosas para acelerar su ejecución
function miFunc(param) {
    if(!miFunc.cache) {
        miFunc.cache = {}; // Creamos un caché de resultados para la función
    }
    if(!miFunc.cache[param]) {
        var resultado = {};
        // ... realiza las operaciones y se calcula resultado ... 
        miFunc.cache[param] = result;
    }
    return miFunc.cache[param];
}
Obviamente, para tareas costosas que no necesiten ejecutarse en primer plano, tenemos los web workers de los que ya he hablado con antelación. Si no se dispone de web workers por la versión del navegador, se puede hacer ejecutar la función en segundo plano mediante setTimeout() como se vió en el capítulo VI de optimización de javascript.

No hay comentarios:

Publicar un comentario