Vía Jonathan Fine, leo un modelo interesante a la hora de diseñar la arquitectura de nuestras aplicaciones Javascript. El artículo es de 2009, pero gracias al auge (o reinvención) del Javascript a nivel de servidor, vuelve a cobrar interés.
El problema
Cuando diseñamos nuestras aplicaciones siempre buscamos mantener el entorno global lo más limpio posible de variables. Para esto, una buena solución es declarar una función anónima autoejecutable que englobe todo nuestro código creando un contexto (closure) propio. Es el patrón de diseño que denominamos de contexto dinámico:
var myApp = {}; ( function( context ){ var foo = 'Hello'; var bar = 'World'; context.sum = function( param1, param2 ){ return param1 + param2; }; context.myMessage = function(){ return foo + ' ' + bar; } } )( myApp ); console.log( myApp.sum( 10, 5 ) ); // 15 console.log( myApp.myMessage() ); // Hello World |
Declaramos una variable a la que asignamos un ‘entorno’ para pasarla como argumento a la función autoejecutable: un buen diseño. Para más información y ejemplos, podemos ojear el artículo Namespancing en Javascript.
El problema con este patrón es que cuando necesitamos añadir algo de forma explícita al ámbito global, la cosa se vuelve poco intuitiva: ¿ usamos this ? ¿ usamos window ?
La solución
Un pequeño truco para solucionar este problema, es crear un objeto global dentro de nuestra función cuyos métodos serán siempre accesibles desde fuera:
( function(){ var global = ( function(){ return this; } ).call(); global.Formula = function (){ // body of function return 'Hello World'; }; } )(); console.log( Formula() ); // Hello World |
Este código sería equivalente a:
var Formula; ( function(){ Formula = function (){ // body of function return 'Hello World'; }; } )(); console.log( Formula() ); // Hello World |
¿Por qué no usamos this.Formula simplemente?
Porque this solo hace referencia al objeto global cuando se declara en el nivel más alto; en estructuras muy anidadas su uso es peligroso.
¿Y por qué no utilizamos window.Formula si el objeto siempre referencia al ámbito global?
Porque window solo funciona correctamente cuando ejecutamos nuestra aplicación en el contexto de un navegador (HTML). Para arquitecturas de escritorio como por ejemplo GNOMEShell o incluso de servidor con Node.js o Rhino, podemos encontrarnos con comportamientos no deseados, de ahí el nuevo interés por este método.
Aplicando call al objeto this, nos aseguramos de que la función es ejecutada en el nivel más alto y, por tanto, referencia al ámbito global sin equívocos.
Y ahora, la versión para el ECMAScript5 Strict Mode
Una de las particularidades del ECMAScript5 strict mode es que cuando invocamos this dentro de la función anónima anterior, siempre obtendremos un valor undefined. Para solucionar este inconveniente, tenemos que echar mano de la documentación hasta encontrar una solución válida: el temido e incomprendido eval.
En la nueva especificación ECMAScript5, el método eval siempre se ejecuta en el ámbito global de la aplicación, independientemente de dónde lo invoquemos. Esto permite resolver el problema de una manera sencilla aunque poco intuitiva: realizando una llamada indirecta al método.
El ejemplo anterior quedaría ahora de la siguiente forma:
//strict-mode ( function(){ "use strict"; var ieval = eval; // Indirect eval call var global = ieval('this'); global.Formula = function (){ // body of function return 'Hello World'; }; } )(); console.log( Formula() ); // Hello World |
Buen artículo Carlos.
Te dejo un par de pequeñas aportaciones. La primera: no es necesario utilizar
call
, es decir, en lugar depodemos usar
La segunda es que en strict mode este truco no funciona.
Gracias joseanpg!
Tienes razón en cuanto al strict-mode ya que en ese caso, this siempre devolvería undefined.
Para solucionarlo, tendríamos que hacer una llamada directa a eval, que siempre se ejecuta en el entorno global.
Quedaría algo así:
Saludos!
Carlos, hay una pequeña errata: esa es una llamada directa a
eval
Un saludo.
Corregido; gracias de nuevo!!