Estudiando el diseño de jQuery paso a paso (II Parte)

28 Sep 2011

Introducción

No tenía pensado hacer una continuación del anterior artículo ‘Estudiando el diseño de jQuery paso a paso‘ pero, a raíz de los comentarios de algunos usuarios, he visto interesante que continuemos profundizando en esta arquitectura para comprender mejor cómo construir nuestros propios frameworks o bibliotecas.

Aplicando métodos por referencia

Uno de los aspectos más interesantes en el uso de jQuery es la aplicación de sus métodos por referencia. Nosotros, en el ejercicio anterior, habíamos creado un objeto donde, para aplicar una funcionalidad concreta a una cadena, ésta se pasaba como argumento del correspondiente método:

$.trim('   Hola Mundo '); // Hola Mundo

Sin embargo, a la hora de trabajar con la API pública de nuestra biblioteca, resulta mucho más cómodo cuando la funcionalidad se aplica sobre un argumento asociado al objeto y no al método. Siguiendo el ejemplo anterior, la nueva propuesta de sintaxis sería:

$('  Hola Mundo   ').trim();

De este modo, ganamos en legibilidad a la vez que abrimos las puertas a otros comportamientos interesantes que veremos más adelante.

Para permitir este tipo de construcciones, tenemos que realizar varios cambios en nuestra biblioteca; modificaciones que van en la línea de lo que hacen otros frameworks y que nos permitirá conocer un poco mejor estas estructuras relativamente complejas del lenguaje.

Lo veremos a continuación en detalle pero, para ir poniéndonos sobre la pista, la idea general es permitir que nuestro objeto se comporte bien como una función o bien como un objeto tradicional según la forma en que sea invocado.

El nuevo patrón

Para este ejemplo, vamos a renombrar nuestro framework como ‘Utils‘ evitando así posibles colisiones con la biblioteca jQuery genuina. Por el mismo motivo, cambiaremos también el álias y utilizaremos ‘$$‘ (doble símbolo de dólar) como método abreviado para llamarla.

La estructura básica quedaría como sigue:

(function (undefined) {
 
  var utils = (function () {
 
  })();
 
  window.utils = window.$$ = utils;
 
})();

Por el momento no hemos realizado ningún cambio; recurrimos al patrón de módulo modificado donde asociamos directamente el objeto creado al ámbito global representado por window.

Ahora, para que los métodos que tengamos en nuestro framework puedan trabajar directamente con los argumentos del objeto general, tenemos que observar un detalle importante que se ve más claro cuando aislamos un caso de uso:

$$('Hola Mundo');

Nuestra biblioteca, así invocada, está funcionando realmente como una función a la que se le asigna un argumento, no como un objeto en el que buscamos un método ($$.myMethod()). Por lo tanto, nuestro framework tiene que comportarse como una función o como un objeto según sea el caso.

Para conseguir el primer objetivo, el que utils responda al comportamiento de una función, crearemos una en su interior que nos servirá de contenedor y que permitirá pasar los argumentos a los métodos que sean llamados a continuación. A esta nueva función la llamaremos core (núcleo) y la implementamos paso a paso de la siguiente forma:

(function (undefined) {
 
  var utils = (function () {
 
    var core = function ( args ) {
      /* ... */
    };
 
    // Methods goes here
 
    // Return
    return core;
 
  })();
 
  window.utils = window.$$ = utils;
 
})();

Tenemos ahora un núcleo general

Hemos creado un nuevo objeto, en realidad una función, que puede recibir sus correspondientes argumentos (en este caso de momento solo args). Si avanzamos hasta el final, vemos un return que devuelve directamente dicho objeto/función. De esta forma estamos haciendo que al escribir,

$$('Hola Mundo');

nuestro framework se comporte como una función normal que recoge parámetros (‘Hola Mundo’). Tenemos ya una parte importante adelantada!

El siguiente paso es sencillo: como sabemos que el flujo de nuestro framework pasa ahora por core cuando utilizamos la fórmula anterior, podemos almacenar sus argumentos en una propiedad del objeto para recuperarla más adelante en los métodos.

El código para conseguir este paso puede parecer complejo a priori pero, una vez analizado con detenimiento, es en realidad muy simple:

(function (undefined) {
 
  var utils = (function () {
 
    var core = function ( args ) {
      return new core.fn.init( args );
    };
 
    core.fn = core.prototype = {
      init: function ( args ) {
        core._args = args;
        return core;
      }
    };
 
    // Methods goes here
 
    // Return
    return core;
 
  })();
 
  window.utils = window.$$ = utils;
 
})();

La jugada es devolver desde core, con return, una nueva función que definimos un poco más adelante. Al utilizar la palabra reservada new, hay que recordar que estamos utilizando dicha función como constructor y que, en realidad, estamos creando un nuevo objeto con los atributos que la componen. De este modo, hemos convertido nuestra función inicial en un objeto que ahora acepta métodos.

En esta estructura, init nos sirve como inicializador mejorado del objeto final: desde aquí, podemos pasar tantos argumentos como necesitemos para, por ejemplo, preconfigurar varios atributos de nuestro objeto final. En este caso, para simplificar el ejercicio, solo pasamos aquello que nos está llegando desde la API pública ( es decir, lo que un usuario haya especificado mediante una sintaxis del tipo $$(‘my string’).myMethod() ).

init coge los argumentos y los asocia (guarda) en el mismo core como una nueva propiedad (_args) que será la que usaremos más adelante cuando tratemos los métodos. Finalmente, devuelve el mismo objeto pero ya modificado y listo para añadirle funcionalidad.

NOTA: Al nombre de la propiedad le hemos añadido un guión bajo para indicar que se trata de un método privado (no accesible desde la API pública); una convención útil cuando trabajamos con objetos grandes.

Los métodos de la API pública

Con nuestros elementos ya accesibles, solo tenemos que comenzar a añadir métodos que los recojan y modifiquen de alguna manera. Como ejemplo, retomaremos de los ejercicios anteriores nuestra función trim() para eliminar los espacios al pricipio y final de una cadena dada.

NOTA: Ya que el código necesario para llevar a cabo esta funcionalidad es muy sencillo, permite que nos concentremos mejor en cómo se implementa en lugar de en cómo trabaja.

Para que los métodos sean accesibles éstos deben ser ahora parte del core, por lo que únicamente tenemos que extender dicho objeto usando el sistema tradicional:

(function (undefined) {
 
  var utils = (function () {
 
     var core = function ( args ) {
      return new core.fn.init( args );
    };
 
    core.fn = core.prototype = {
      init: function ( args ) {
        core._args = args;
        return utils;
      }
    };
 
    // Methods goes here
    core.trim = function (str) {
      core._args && (str = core._args);
      return str.replace(/^\s+|\s+$/g, '');
    };
 
    // Return
    return core;
 
  })();
 
  window.utils = window.$$ = utils;
 
})();

Como vemos, la idea es que cada método que implementemos extienda al núcleo con una nueva función. En este caso, es interesante observar que podemos recoger un argumento. Esto es así para conservar la compatibilidad con la forma anterior. Es decir, que podemos obtener el mismo resultado tanto pasándole un argumento al objeto general como al método en particular:

console.log( $$.trim( '  Hola Mundo  ') ) ;  // Retrocompatibilidad con la forma anterior.
console.log( $$('  Hola Mundo  ').trim() ) ; // Nueva invocación por declaración.

Para que el framework sepa de dónde tomar el parámetro, hemos añadido un condicional:

core._args && (str = core._args);

Esto lo que hace es preguntar si existen argumentos ya cacheados. En caso de que existan, los tomamos como argumentos; si no, buscamos aquellos pasados directamente al método trim().

La línea de código anterior, aunque parece compleja, es un simple shotcut de las siguientes:

if( core._args ){
  str = core._args
}

La sintaxis propuesta funciona porque, como hemos mencionado en varias ocasiones, Javascript es un lenguaje de circuito corto (o cortocircuito). Esto quiere decir que cuando realizamos comparaciones lógicas (booleanas), los términos se evalúan de izquierda a derecha rompiéndose inmediatamente la secuencia tras la primera disconformidad. En este condicional, si la primera parte de la expresión es correcta (si existe core._args), se evalúa el siguiente término. Dicho término (str = core._args) es una expresión, por lo que será ejecutada sin más. Es una forma elegante de ahorrarnos un if y aligerar (aunque sea de forma virtual) la complejidad ciclomática del código.

Sin embargo, falta un paso más para que esto funcione: en caso de que el usuario haya utilizado la forma anterior de enviar su argumento asociado al método, tenemos que decirle a nuestro código que lo coja de ahí. También es necesario borrar el argumento cacheado tras su uso para que a la siguiente invocación del método, si no hemos asignado ningun elemento sobre el que actuar, no se utilice el que teníamos guardado de la vez anterior.

Por lo tanto, para llevar a cabo estas dos modificiaciones, necesitamos un par de líneas nuevas justo detrás de la que acabamos de explicar:

core._args && (str = core._args);
str || (str = '');
core._args = '';

De nuevo se comprueba si str se corresponde con un valor nulo o indefinido en cuyo caso, lo reasignamos como cadena vacía.

La siguiente línea únicamente borra cualquier valor almacenado en el atributo core._args para que no sea reutilizado la proxima vez que se utilice la biblioteca.

Como mejora rápida, para aligerar un poco el código, las dos primeras líneas que hemos indicado pueden refactorizarse utilizando el mismo principio del cortocircuito anterior. Quedaría como sigue:

str = core._args || str || '';
core._args = '';

Si lo unimos todo, nuestro código tiene ahora el siguiente aspecto:

(function (undefined) {
 
  var utils = (function () {
     var core = function ( args ) {
      return new core.fn.init( args );
    };
 
    core.fn = core.prototype = {
      init: function ( args ) {
        core._args = args;
        return utils;
      }
    };
 
    // Methods goes here
    core.trim = function (str) {
      core._args && (str = core._args);
      str || (str = '');
      core._args = '';
      return str.replace(/^\s+|\s+$/g, '');
    };
 
    // Return
    return core;
 
  })();
 
  window.utils = window.$$ = utils;
 
})();

Bien! Ahora toca probarlo para ver si funciona como se espera:

var sample1 = '  Hello World  ';
var sample2 = '   Good bye Lenin    ';
 
console.log( '1', $$.trim( sample1 ) ); // 'Hello World'
console.log( '2', $$.trim( sample2 ) ); // 'Good bye Lenin'
console.log( '3', $$( sample1 ).trim() ); // 'Hello World'
console.log( '4', $$( sample2 ).trim() ); // 'Good bye Lenin'
console.log( '5', $$.trim( sample2 ) ); // 'Good bye Lenin'
console.log( '6', $$.trim() ); // ''
console.log( '7', $$().trim() ); // ''
console.log( '8', $$.trim() ); // ''
console.log( '9', $$( sample1).trim( sample2 ) ); // 'Hello World'
console.log( '10', $$().trim() ); // ''
console.log( '11', $$( sample1 ).trim( sample2 ) ); // 'Hello World'
console.log( '12', $$.trim() ); // ''
console.log( '13', $$( sample1 ).trim( sample2 ) ); // 'Hello World'
console.log( '14', $$().trim() ); // ''
console.log( '15', $$( sample1 ).trim( sample2 ) ); //'Hello World'

Mejorando un poco el patrón

Llegados a este punto, con nuestra estructura básica ya completa y con uno de sus posibles métodos ya implementados, podríamos decir que nuestro framework es funcional: los resultados son los esperados y el código resulta sencillo de extender y mantener. Sin embargo, como desarrolladores, podemos estar ya pensando en varias mejoras que complementen el trabajo realizado hasta ahora. Por ejemplo:

– Una fuerte comprobación de tipos en la entrada de argumentos.
– Un robusto sistema de manejo de excepciones para casos inesperados.
– Implementar un motor tipo Sizzle para ofrecer así soporte completo al API DOM.
– …

Aunque todo lo anterior podría resultar un buen ejercicio para comprobar si nos manejamos bien con esta estructura, quedaría pendiente una feature muchos más interesante: implementar un patrón de diseño encadenado que permita enlazar varios métodos sobre una misma referencia/entrada.

La finalidad de esta mejora sería conseguir una sintaxis similar a:

$$('  Hello World  ').trim().method2().method3(); // And so on...

En una próxima entrega, veremos qué cambios tenemos que realizar en nuestro framework para conseguir este comportamiento, las ventajas que presenta y también sus inconvenientes en lo que a calidad de código se refiere. En relación a esto último, haremos alusión a la conocida Ley de Demeter y a cómo la enfrentan otras bibliotecas similares.

Conclusión

En este artículo hemos visto como, partiendo del patrón tipo jQuery que presentamos en otro artículo, podemos conseguir una arquitectura mejorada de gran flexibilidad a la hora de aplicar distintas funcionalidades sobre aquellos elementos en los que lo necesitemos.

Finalmente, hemos presentado una mejora importante (aún sin implementar) como es la que ofrece el patrón encadenado (chaining pattern) gracias a la cual, podremos aplicar varios métodos (funciones) sobre un mismo argumento en lo que resulta una sintaxis muy clara y legible.

Más:

{22} Comentarios.

  1. Alberto

    Muy bueno el articulo pero hay una parte que no entendí:
    core.fn = console.prototype = {…}
    ¿Esa línea que es? ¿uUna herencia prototipica?
    ¿
    Porque no simplemente hacer: core.fn = {…}
    ?

    • Carlos Benítez

      Hola Alberto;
      la idea es que la función core.fn.init resulta crítica para añadir funcionalidad al objeto general ya que es la que se invoca cada vez que utilizamos $$(‘myString’).

      Asignando core.fn como prototipo de la función y teniendo en cuenta que es usada como constructor gracias al comando anterior new ( return new core.fn.init( args ) ), garantiza que cualquier nueva funcionalidad posterior añadida mediante core.fn.anotherMethod estará inmediatamente disponible para cualquier objeto devuelto por utils.

      No hemos hablado aún de ello pero gracias a lo anterior, podemos extender nuestra biblioteca con plugins muy fácilmente. Por ejemplo:

      utils.fn.myFirstPlugin = function () { console.log("Hello World"); };
      $$('myString').myFirstPlugin(); // Hello World
      

      Lo anterior funciona porque el método myFirstPlugin se está añadiendo directamente al prototipo de todos los objetos devueltos por utils en el momento que pasamos por el constructor (new).

      Saludos!

  2. señor
    var pum = $$("     Hello, strange world   ");
    pum.trim();
    pum.trim(); // PUUMM!!
    
  3. gnz/vnk

    A raíz del comentario anterior, que deja a la vista un problema claro, me pregunto una cosa, Carlos.

    Si es una decisión intencionada la de limpiar core._args «para que no sea reutilizado» después del uso de trim(), entonces ¿qué sentido tiene usar este tipo de arquitectura? Quiero decir, $$(«shkhsdf») crea un objeto pero sólo es usable 1 vez, porque así lo has decidido. Entonces ¿para qué merece la pena crear ese objeto? ¿Por qué todo el esfuerzo (y coste en rendimiento) adicional, si sólo permite 1 uso?

    No sé, pero en mi modesta opinión, esa decisión le quita completamente el sentido a todo esto y lo hace bastante absurdo.

    • Carlos Benítez

      Hola Gonzalo!

      Claro que no me molesta tu opinión; de hecho, el comentario es muy acertado y tienes en parte mucha razón.

      La cuestión es que, para no hacer un artículo demasiado largo, he dejado al framework sin varias características importantes; una de ellas es, como he comentado más arriba, la posibilidad de encadenar varios métodos sobre el mismo argumento.

      Al cortar ahí el código y hacer varias pruebas me di cuenta de que los argumentos se quedaban guardados tras cada llamada. Y es perfectamente lógico ya que el mecanismo para limpiarla se realiza en ese punto al que no hemos llegado todavía (así lo hace jQuery y ZeptoJS). Es por eso que, para que el artículo fuera ‘funcional’ tal cual estaba, tuve que permitirme la licencia de forzar el borrado de args directamente en cada método que se añada. Y, como bien dices, esa fórmula no resulta la mejor.

      En el siguiente post sobre el tema, cuando implementemos la cadena de métodos, tendremos necesariamente que modificar parte de lo que ahora tenemos y, de las primeras cosas que eliminaremos, serán precisamente esas líneas.

      No creo que te haya convencido, pero es que no encontré otra forma de hacer esta serie de artículos didáctica manteniendo una extensión razonable. La idea es que tras cada post, se pueda ejecutar el código final obteniendo algún tipo de funcionalidad.

      Saludos!

  4. Fran

    Hola,

    Ante todo, felicidades con la línea del diseño de jQuery, estoy encantado con los artículos y los sigo con impaciencia.

    Con respecto a la comparación lógica no sería mejor hacer:

    str || ( ( str = core._args ) && ( core._args = » ) );

    por:

    core._args && (str = core._args);
    str || (str = »);
    core._args = »;

    Si no nos pasan argumentos que coja el selector y aparte que lo inicialize.

    Con respecto a la extensión de plugins, supongo que habrá primero que extender los métodos de core.fn o algo así, no???

    Un saludo,

    • Carlos Benítez

      Hola Fran;
      tu refactorizado fallaría para la siguiente prueba:

      $$.trim();
      

      Si no pasamos argumentos ni a un lado ni a otro, str estaría vacía.

      De todos modos, como le he comentado a gnz/vnk más arriba, todo lo que tiene que ver con la recepción de argumentos y su limpiado lo abordaremos de lleno en el siguiente post sobre el tema. De momento, lo podemos dejar tal cual porque nos sirve para mostrar el funcionamiento general de esta arquitectura.

      La referencia anterior a los plugins ha sido adelantarse un poco, pero la estudiaremos también. De momento, con el ejemplo que puse arriba, sabemos que podemos extender fácilmente el prototipo del objeto utils. Veremos cómo aprovechar esa posibilidad más adelante!

      Saludos!

  5. Fran

    Hola Carlos,

    Tienes razon con:

    $$.trim();
    

    No me di cuenta por que yo utilizo la función toType de anteriores articulos en el trim:

    if( toType( str ) == 'string' )
      return str.replace(/^\s+|\s+$/g, '');
    else
      return 'No es un String';
    
    toType = function(obj){
      return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
    };
    

    Permitiendome pasar por el else y poder controlarlo. De todas maneras, como bien dices el ejemplo es funcional segun esta y explica lo que tiene que explicar.

    Impaciente estoy para las siguientes entregas.

    Un saludo.

  6. Alberto

    Excelente Carlos, muchas gracias por aclarar la duda, de verdad nunca imaginé que la cuestión viniera por ahí pero la verdad es muy intersante. Me llega a la mente otro duda sobre la misma linea, ¿por qué se usa el objeto el prototipo del objeto «console»?

    • Carlos Benítez

      Ups! Ha sido una errata del autocompletado del IDE; obviamente, tenemos que atacar al prototipo del objeto core

      Expandir la consola de Firebug no tiene mucho sentido aquí XD

      Gracias por el apunte!

  7. pepo

    Muy bueno el post, me resultó muy interesate y práctico.

    Saludos!

  8. Victor

    Hola Carlos,

    Como siempre estupendo cada articulo que haces, tenia una duda, espero no caer en lo tonto, pero en la parte de:

    core.fn = core.prototype = {
    init : function(args){
    core._args = args;
    console.log(«argumento: » + args);
    return core;
    }
    }

    y en la de aqui:

    core.trim = function(str){
    core._args && (str = core._args);
    /*str || (str = »);
    core._args = »;*/
    return str.replace(/^\s+|\s+$/g, »);
    }

    si estoy extendiendo core, para la funcion trim xq no agrego:

    core.prototype.trim

    tal vez sea no comprendi bien, pero me salta esa detalle,

    Saludos

  9. joseanpg

    La verdad es que no termino de ver las ventajas de

    core._args && (str = core._args);

    33

    frente a

    if (core._args) str = core._args;
  10. Alberto

    Victor, según lo que entiendo, el trim se hace de esa forma porque esta dentro del mismo scope de «core», es decir puedo extenderlo así porque aún no se ha hecho el return. Una vez que ya se ha retornado no es posible extenderlo de esa forma porque solo afectaría a la instancia actual, para extender a todas las instancias debo añadirlo directamente el prototipo, es decir usando $$.fn = function(){…}
    Es lo entiendo, no se si me expliqué bien, esperemos que Carlos amplie o corrija la información.

  11. joseanpg

    Si estás intentando reproducir el comportamiento de jQuery tu función init va en contra del «espíritu» de dicho framework:

    var utils = (function () {
     
        var core = function ( args ) {
          return new core.fn.init( args );
        };
     
        core.fn = core.prototype = {
          init: function ( args ) {
            core._args = args;
            return core;
          }
        };
     
        // Methods goes here
     
        // Return
        return core;
     
      })();
    

    Me explico: la función referenciada por core es una función que construye por delegación «objetos jqueryanos». Dicha función será exportada como las propiedades utils y $$ del objeto global.

    Cuando se invoca dicha función, se creará mediante new un nuevo «objeto jqueryano» utilizando como constructor core.prototype.init.

        var core = function ( args ) {
          return new core.fn.init( args );
        };
    

    Esto es importante, el constructor no es core, sino core.prototype.init. La función core no se utilizará en ningún momento como constructor y por tanto la mención a su prototipo asociado distrae la atención de lo fundamental. ¡Ojo! El prototipo del objeto devuelto no es core.prototype, sino core.prototype.init.prototype y luego tendremos que arreglar esto. Hay alternativas más claras, pero estamos hablando de jQuery (ya me entendéis 🙂

    Bien, estudiemos al constructor de «objetos jqueryanos»: core.prototype.init.
    Necesita un objeto fresco que puede ser el pasado por new vía this o uno nuevo que creemos dentro de core.prototype.init. Veamos como lo hace el código de Carlos:

        core.prototype = {
          init: function ( args ) {
            core._args = args;
            return core;
          }
        };
    

    ¡Vaya! No se nos devuelve un objeto recién salido del horno, sino todo lo contrario: se devuelve … ¡la función core! ¿Dónde están nuestros «objetos jqueryanos»?

    Sinceramente, creo que si deseas tener una buena serie de artículos sobre el interior de jQuery no sería mala idea editar ese código y ahorrarle a los lectores un buen número de dificultades.

    Un saludo.

    • Carlos Benítez

      Si queremos ir despacio y ser didácticos, hay que pasar por todas las etapas del proceso. A cada artículo, se harán las modificaciones, refactorizados y cambios estructurales que sean necesarios. Como comenté más arriba, para que al final de cada entrada el código sea funcional, hay que permitirse determinadas licencias.

      Creo que si me hubiera limitado a soltar todo el código fuente de jQuery de golpe, no aprenderíamos nada. Sin embargo, precisamente yendo a este ritmo, vamos haciéndonos con la dinámica, ampliando un poco nuestros conceptos de Javascript y encontrando estos problemas que comentas.

      Tomad esta serie como si fuera un ejercicio de TDD (perdonad la analogía los más puristas): nos proponemos algo y construimos el mínimo código necesario para que funcione; luego refactorizamos. A cada nueva especificación, ya nos plantearemos qué cambios son necesarios.

      Saludos!

  12. gnz/vnk

    >Al cortar ahí el código y hacer varias pruebas me di cuenta de que los argumentos
    >se quedaban guardados tras cada llamada. Y es perfectamente lógico ya que el
    >mecanismo para limpiarla se realiza en ese punto al que no hemos llegado todavía
    >(así lo hace jQuery y ZeptoJS).

    No quiero presuponer nada, pero este comentario (unido al problema comentado) me hace dudar si tienes claro o no, la diferenciación que jQuery hace entre $(bla).metodo() y $.metodo(bla) (o $().metodo(bla) en algún caso). Lo digo porque estás tratando de usarlo como si únicamente fuera «syntactic sugar», como si ambas cosas se debieran tratar como equivalentes, y eso es lo que lleva finalmente al conflicto con los parámetros. Y en realidad, jQuery diferencia muy claramente entre ambas cosas y no las trata en absoluto como equivalentes (aunque haya un par de casos que pueden llevar a confusión).

    O eso o que realmente no entiendo qué sentido tiene querer «limpiar los argumentos tras cada llamada».

    No sé, quizá sería mejor seguir una estrategia diferente a la hora de escribir estas cosas. En lugar de partir de algo más grande y cortar trozos y luego apañarlo para que «funcione», empieza construyendo desde cero, siguiendo el camino desde el principio, en lugar de intentar reconstruirlo desde el final.

  13. gnz/vnk

    Por dejar un ejemplo claro sobre lo que dice Jose de devolver core (o devolver utils más adelante):

    ¿Qué sentido tiene que ocurra esto? ¿De qué sirve?

    var a = $$(«Hello, nice person»);
    var x = $$(«Die, creep!»);
    a.trim(); // Say whaaaa?
    x.trim(); // Eeeeeh?

    Como digo, no sé por qué será. Será porque has cortado el código a medio hacer o porque has tenido que apañar algo o lo que sea, pero claramente carece de sentido.

  14. Juan

    @gnz mas alla de los errores cometidos por Carlos en pos de la simplificacion, no es completamente absurdo esperar que ciertos metodos se puedan llamar de forma $.metodo() o $(foo).metodo(), en particular para metodos del DOM. Prototype hace eso y no es tan terrible http://api.prototypejs.org/dom/Element/.

    Pero de todas maneras, el acercamiento no es correcto. Las funciones que se puedan llamar de ambas formas no pueden ser las mismas o se cae en el problema de la inconsistencia de los argumentos que esta sufriendo Carlos. Y no estoy seguro de que lo peor sea que no funcionen los ejemplos que muestran sino que $(«a»).trim(«b») cambia completamente el sentido de la ejecucion, haciendo muy dificil la lectura del codigo a posteriori.

  15. Juan

    Esta seria una manera de lograr ese objetivo:

    function Library(items) {
    this._items = items;
    }
    Library.addMethod = function (name, fn) {
    Library[name] = fn;
    Library.prototype[name] = function () {
    return fn.apply(this, [this._items].concat(Array.prototype.slice.call(arguments)));
    };
    };

    De esta manera se logra que se pueda llamar a $.foo(bar) y a $(bar).foo(), pero se mantiene que $(bar).foo(baz) no es lo mismo que $.foo(baz).

    • Carlos Benítez

      Hola Juan; gracias por el ejemplo!

      Es una forma interesante de dividir las funciones dependiendo de cómo han sido llamadas para evitar la inconsistencia actual.

      Sin embargo, para la próxima entrega mantendremos la filosofía de jQuery y separaremos el tipo de método según la posición de los argumentos: manejo del DOM para aquellos que llegan como primer término y manipulación de cadenas o similares para los segundos.

      Saludos!

  16. Daniel

    Hola:

    Tengo una consulta. En este segundo artículo en particular sobre la arquitectura JQuery, hay un detalle que no me queda claro, y tiene que ver con la creación de los objetos ‘utils’ (que vendría a ser su jQuery).

    En concreto, si uno intenta lo siguiente:

    var a = utils(» Hola «);
    var b = utils(» ¿Qué tal? «);
    var c = utils(» ¿Qué se cuenta? «);

    alert(a.trim()); // Muestra «¿Qué se cuenta?», en lugar de «Hola».
    alert(b.trim()); // Muestra «¿Qué se cuenta?», en lugar de «¿Qué tal?».

    Podrá ver que el ‘alert’ muestra «¿Qué se cuenta?» en todos los casos (es decir, la última cadena definida). Eso significa que la función de inicialización no está creando nuevas instancias, sino que devuelve siempre la misma, a la cual le establece la propiedad ‘_args’ con la cadena del último objeto creado.

    Si me puede aclarar qué ocurre exactamente, se lo agradecería.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *