2010-01-14 5 views
9

He estado desarrollando complementos jQuery desde hace bastante tiempo, y me gusta pensar que sé cómo diseñar uno bien por ahora. Sin embargo, uno de los problemas sigue fastidiándome, y es cómo lidiar con las funciones privadas de una manera poderosa pero elegante.jQuery plugin patrón de diseño (práctica común?) Para tratar con funciones privadas

Mis plugins generalmente buscan algo como esto:

(function($) { 

    $.fn.myplugin = function(...) { 
    ... 
    // some shared functionality, for example: 
    this.css('background-color', 'green'); 
    ... 
    }; 
    $.fn.mypluginAnotherPublicMethod = function(...) { 
    ... 
    // some shared functionality, for example: 
    this.css('background-color', 'red'); 
    ... 
    }; 

}(jQuery)); 

Ahora mi pregunta es: ¿cómo secar cuidadosamente hasta que la funcionalidad compartida? Una solución obvia sería para ponerlo en una función dentro del espacio de nombres del plugin:

var fill = function($obj, color) { 
    $obj.css('background-color', color); 
}; 

Aunque esta solución es efectiva y bien espacio de nombres, no me gusta la misma. Por una simple razón: tengo que pasarle el objeto jQuery. Es decir. Tengo que llamarlo así: fill(this, 'red');, mientras que quisiera llamar así: this.fill('red');

Por supuesto que podía lograr este resultado con sólo poner en filljQuery.fn. Pero eso se siente muy incómodo. Imagine tener diez complementos desarrollados en base a este enfoque y cada complemento que coloca cinco de esas funciones "privadas" en el espacio de nombres de la función jQuery. Termina en un gran desastre. Podríamos mitigar prefijando cada una de estas funciones con el nombre del complemento al que pertenecen, pero eso realmente no lo hace más atractivo. Se supone que estas funciones son privadas para el complemento, por lo que no queremos exponerlas al mundo exterior (al menos no directamente).

Así que ahí está mi pregunta: ¿alguien de ustedes tiene sugerencias de cómo obtener lo mejor de ambos mundos. Es decir; el código de complemento puede invocar funciones de complemento 'privadas' de forma similar a this.fill('red') (o this.myplugin.fill('red') o incluso this.myplugin().fill('red') etc.), al tiempo que evita la contaminación del espacio de nombres de la función jQuery. Y, por supuesto, debería ser ligero, ya que estas funciones privadas podrían ser llamadas con mucha frecuencia.


ACTUALIZACIÓN: Gracias por sus sugerencias.

Me gusta especialmente la idea de David de definir un tipo de objeto que contiene las funciones 'privadas' y envuelve un objeto jQuery. El único problema es que todavía me impide encadenar funciones 'privadas' y 'públicas'. Lo cual era una gran razón para querer una sintaxis como this.fill('red') para comenzar.

que terminó con una solución que yo no considero tremendamente elegante, pero apelando a la 'mejor de ambos mundos' causa:

$.fn.chain = function(func) { 
    return func.apply(this, Array.prototype.slice.call(arguments, 1)); 
}; 

que permite construcciones como:

this. 
    find('.acertainclass'). 
    chain(fill, 'red'). 
    click(function() { 
     alert("I'm red"); 
    }); 

I crucé mi pregunta en otros lugares, que también recolectó algunas respuestas interesantes:

Respuesta

2

¿Qué tal (dentro del alcance del plug-in):

var fill = function() 
{ 
    (function (color) 
    { 
     this.css ('backgrorund-color', color); 
     //.. your stuff here ... 
    }).apply (this, arguments); 
} 

$.fn.myplugin = function() 
{ 
    fill ('green'); 
} 

De esta manera, llenar retendrá el contexto de jQuery que se encuentre, y sigue siendo privada de su plugin


Enmendado: el anterior es incorrecto wrt de alcance, Intente lo siguiente en su lugar:

var fill = function (color) 
{ 
    if (!$this) return; // break if not within correct context 

    $this.css ('backgrorund-color', color); 
    //.. your stuff here ... 
} 

$.fn.myplugin = function() 
{ 
    var $this = $(this); // local ref to current context 
    fill ('green'); 
} 
+1

Entonces, ¿cómo llenar() mantenerse en el 'esto' de $ .fn.myplugin()? –

+0

no sería 'this' la función' (función (color) {...}) '? – nickf

+0

Creo que estos son los 'this''es en este ejemplo de código: (1)' $ obj.myplugin() ':' $ obj' (2) 'fill()': 'window' (3) 'function (color)': 'window' –

0

Desafortunadamente, los métodos "privado" (o cualquier propiedad para el caso) pueden no ser llamados con un "presente" como prefijo en javascript. Todo lo que se llame como this.myFunc(myArgs) debe estar a disposición del público.

Y los métodos "privados" solo se pueden llamar desde el ámbito en el que se definieron.

Su solución original es la única que funcionará. Sí, es un dolor tener que pasar en this, pero no hay más nivel de detalle que habría si su petición imposible era posible:

this.fill('green'); 
//or 
fill(this,'green'); 

Como se puede ver, los dos se ocupan exactamente el mismo número de caracteres tu codigo.

Siento decirlo, pero le pegan con esto como una solución, a menos que desee crear un nuevo espacio de nombres y hacerlos no privada - que simplemente se va a añadir a la cantidad de código que necesita para escribir , es decir, lo que indirectamente llamados "no directamente expuesto":

this.myplugin.fill('green'); 

... es más detallado, por lo tanto tipo de derrota el propósito.

Javascript no es como otros idiomas, no hay miembros "privados" per-se, solo miembros accesibles dentro de cierres, que a veces se pueden utilizar de manera similar a los miembros privados, pero es más como una "solución", y no a los miembros privados de "trato real" que estás buscando.

Puede ser difícil aceptar esto (a menudo me cuesta), pero no intente moldear Javascript en otros idiomas, tómelo como lo que es ...

11

Una cosa primero: si desea llamar algo así como this.fill('red'); donde este es una instancia de jQuery, hay que extender el prototipo de jQuery y hacer fill() "público". jQuery proporciona directrices para extender su prototipo utilizando los llamados "complementos" que se pueden agregar usando $.fn.fill, que es lo mismo que jQuery.prototype.fill.

En las devoluciones de llamada de jQuery, , esta es a menudo una referencia al elemento HTML, y no puede agregar prototipos a esos (todavía). Ese es uno de los motivos por los cuales jQuery envuelve elementos y devuelve instancias jQuery que se pueden extender fácilmente.

Usando la sintaxis (function(){})();, puede crear y ejecutar javascript "privado" sobre la marcha, y todo desaparece cuando termina. Con esta técnica, puedes crear tu propia sintaxis similar a jQuery que envuelve jQuery en tu propio objeto privado encadenable.

(function(){ 
    var P = function(elem) { 
     return new Private(elem); 
    }; 
    var Private = function(elem) { 
     this.elem = jQuery(elem); 
    } 
    Private.prototype = { 
     elem: null, 
     fill: function(col) { 
      this.elem.css('background',col); 
      return this; 
     }, 
     color: function(col) { 
      this.elem.css('color', col); 
      return this; 
     } 
    } 

    $.fn.myplugin = function() { 
     P(this).fill('red'); 
    }; 
    $.fn.myotherplugin = function() { 
     P(this).fill('yellow').color('green'); 
    }; 

})(); 

$('.foo').myplugin(); 
$('.bar').myotherplugin(); 

console.log(typeof P === 'undefined') // should print 'true' 

De esta manera, la P representa su propia caja de herramientas de funciones "privadas". No estarán disponibles en ningún otro lugar del código ni en el espacio de nombres de jQuery, a menos que los adjunte a alguna parte. Puede agregar tantos métodos como desee en el objeto privado, y siempre que devuelva este, también puede encadenarlos al estilo jQuery como lo hice en el ejemplo.

2

Es posible que desee echar un vistazo a cómo se implementa el jQuery UI Widget Factory.

El enfoque básico es la siguiente:

(function($){ 
    $.fn.myplugin = function(method) 
    { 
     if (mp[method]) // map $('foo').myplugin('bar', 'baz') to mp.bar('baz') 
     { 
      return mp[method].apply(this, Array.prototype.slice.call(arguments, 1)); 
     } 
     else if (typeof method === 'object' || ! method) 
     { 
      return mp.init.apply(this, arguments); // if called without arguments, init 
     } 
     else 
     { 
      $.error('Method ' + method + ' does not exist on $.myplugin'); 
     } 
    }; 

    // private methods, internally accessible via this.foo, this.bar 
    var foo = function() { … }; 
    var bar = function() { … }; 

    var private = { // alternative approach to private methods, accessible via this.private.foo 
     foo : function() { … }, 
     bar : function() { … } 
    } 

    var mp = { // public methods, externally accessible via $.myplugin('foo', 'bar') 
     init : function(options) 
     { 
      return this.each(function() 
      { 
       // do init stuff 
      } 
     }, 
     foo : function() { … }, 
     bar : function() { … } 
    }; 
})(jQuery); 
+0

que desearía usar que no sea 'private' para un nombre de variable, ya que es una palabra reservada (para uso futuro) en ECMAScript – jinglesthula

+0

@jinglesthula ¡Buena captura, gracias! – vzwick

Cuestiones relacionadas