2010-05-06 19 views
23

Normalmente, he visto prototipos de las funciones declaradas fuera de la definición de clase, así:configuración de JavaScript prototipo de la función dentro de la declaración de clase de objeto

function Container(param) { 
    this.member = param; 
} 
Container.prototype.stamp = function (string) { 
    return this.member + string; 
} 

var container1 = new Container('A'); 
alert(container1.member); 
alert(container1.stamp('X')); 

Este código produce dos alertas con los valores "A" y "AX" .

Me gustaría definir la función prototipo DENTRO de la definición de la clase. ¿Hay algo de malo en hacer algo como esto?

function Container(param) { 
    this.member = param; 
    if (!Container.prototype.stamp) { 
     Container.prototype.stamp = function() { 
      return this.member + string; 
     } 
    } 
} 

Estaba intentando esto para poder acceder a una variable privada de la clase. Pero he descubierto que si mi prototipo de la función hace referencia a una var privada, el valor de la variable privada es siempre el valor que se utilizó cuando la función prototipo fue creado inicialmente, no el valor en la instancia del objeto:

Container = function(param) { 
    this.member = param; 
    var privateVar = param; 
    if (!Container.prototype.stamp) { 
     Container.prototype.stamp = function(string) { 
      return privateVar + this.member + string; 
     } 
    } 
} 
var container1 = new Container('A'); 
var container2 = new Container('B'); 
alert(container1.stamp('X')); 
alert(container2.stamp('X')); 

Este código produce dos alertas con los valores "AAX" y "ABX". Esperaba que la salida fuera "AAX" y "BBX". Tengo curiosidad de por qué esto no funciona, y si hay algún otro patrón que pueda usar en su lugar.

EDIT: Tenga en cuenta que estoy totalmente de entender que para este ejemplo sencillo sería mejor utilizar sólo un cierre como this.stamp = function() {} y no utilizar prototipo en absoluto. Así es como yo lo haría también. Pero estaba experimentando con el uso de prototipos para obtener más información al respecto y me gustaría saber algunas cosas:

  • ¿Cuándo tiene sentido utilizar prototipos en lugar de cierres? Solo necesito usarlos para extender objetos existentes, como Date. He leído que closures are faster.
  • Si necesito usar una función de prototipo por algún motivo, ¿es "correcto" definirla DENTRO de la clase, como en mi ejemplo, o debería definirse en el exterior?
  • Me gustaría entender por qué el valor de privateVar de cada instancia no es accesible para la función de prototipo, solo el valor de la primera instancia.
+0

cierres tachado de nuevo ... – Dormilich

+2

leer sobre los cierres (http://www.jibbering.com/faq/faq_notes/closures.html) por razones detalladas por las que su código se comporta de la manera que lo hace. – outis

Respuesta

21

¿Cuándo tiene sentido utilizar funciones de prototipo en lugar de cierres?

Bueno, es la forma más ligera que ir, digamos que usted tiene un método en el prototype de cierta constructor, y se crea 1000 instancias de objetos, todos los objetos tendrán su método en su cadena de prototipo, y todos de ellos se referirá solo a un objeto de función.

Si inicializas ese método dentro del constructor, p. (this.method = function() {};), todas sus 1000 instancias de objetos tendrán un objeto de función como propiedad propia.

Si necesito usar una función de prototipo por alguna razón, ¿está "OK" para definirla DENTRO de la clase, como en mi ejemplo, o debería definirse en el exterior?

Definir los miembros del prototipo de un constructor dentro de sí mismo, no tiene mucho sentido, le explicaré más sobre él y por qué su código no funciona.

me gustaría entender por qué el valor privateVar de cada instancia no se puede acceder a la función prototipo, sólo el valor de la primera instancia.

Veamos el código:

var Container = function(param) { 
    this.member = param; 
    var privateVar = param; 
    if (!Container.prototype.stamp) { // <-- executed on the first call only 
     Container.prototype.stamp = function(string) { 
      return privateVar + this.member + string; 
     } 
    } 
} 

El punto clave sobre el comportamiento de código es que la función Container.prototype.stamp se crea en la primera invocación de método.

En el momento en que crea un objeto de función, almacena el ámbito adjunto actual en una propiedad interna llamada [[Scope]].

Este alcance se aumenta más tarde cuando llama a la función, por los identificadores (variables) declarados dentro de ella usando var o una FunctionDeclaration.

Una lista de propiedades [[Scope]] forma la cadena alcance, y cuando se accede a un identificador (como la variable de privateVar), estos objetos son examinados.

Y dado que su función se creó en la primera invocación de método (new Container('A')), privateVar está vinculado al Alcance de esta primera llamada de función, y seguirá estando vinculado independientemente de cómo llame al método.

Dale un vistazo a este answer, la primera parte es sobre la declaración with, pero en la segunda parte hablo de cómo funciona la cadena de alcance para las funciones.

+0

@CMS: ¡Gran explicación, muchas gracias! Entonces, si mi función de prototipo solo accede a 'this' y no a vars privados, entonces funcionaría correctamente, aunque esté definida DENTRO de la clase, ¿verdad? ¿Hay algún inconveniente para hacerlo de esa manera en lugar de declarar el prototipo después de la clase como normalmente veo? – Tauren

+1

@Tauren, sí, hay un inconveniente, tendrá una pérdida de memoria, por ejemplo, en su código, todas las variables declaradas en la primera invocación dentro de su constructor no serán basura, porque como saben ahora, el alcance adjunto desde donde se crea la función 'Container.prototype.stamp' permanece accesible incluso después de que el constructor finaliza su ejecución (se crea un cierre). Por esa razón, algunas bibliotecas como [Cierre de Google] (http://code.google.com/closure/library/) evitan cierres para los miembros * privados, se limitan simplemente a las convenciones de nombres, p. 'obj .__ privateMember'. – CMS

+0

tiene perfecto sentido, gracias! – Tauren

1

que necesita para poner la función en cada caso específico en lugar del prototipo, así:

Container = function(param) { 
    this.member = param; 
    var privateVar = param; 

    this.stamp = function(string) { 
     return privateVar + this.member + string; 
    } 
} 
+0

@Slaks: He revisado la pregunta para tener más claro exactamente qué estoy preguntando. – Tauren

1

Para obtener el comportamiento que desea que necesita para asignar a cada objeto individual separadas stamp() funciones con cierres únicos :

Container = function(param) { 
    this.member = param; 
    var privateVar = param; 
    this.stamp = function(string) { 
     return privateVar + this.member + string; 
    } 
} 

al crear una única función en el prototipo cada objeto utiliza la misma función, con el cierre función sobre el primer contenedor de privateVar.

Al asignar this.stamp = ... cada vez que se llama al constructor, cada objeto obtendrá su propia función stamp(). Esto es necesario ya que cada stamp() necesita cerrarse sobre una variable privateVar diferente.

+0

@John: gracias por su respuesta. Mi pregunta no era lo suficientemente clara, supongo, así que la revisé. Por favor, eche un vistazo a la edición. – Tauren

0

Esto es porque privateVar no es un miembro privado del objeto, sino parte del cierre del sello.Usted podría conseguir el efecto de crear siempre la función:

Container = function(param) { 
    this.member = param; 
    var privateVar = param; 
    this.stamp = function(string) { 
     return privateVar + this.member + string; 
    } 
} 

El valor de privateVar se establece cuando la función está construido, por lo que necesita para crear cada vez.

EDITAR: modificado para no configurar el prototipo.

+0

@Kathy: Gracias, acepto que un cierre es el camino a seguir. Por favor, vea mi pregunta editada, ya que agregué más detalles sobre exactamente lo que estoy tratando de preguntar. – Tauren

+0

@Tauren La razón por la cual la variable solo se configuró en el primer privateVar es que la función solo se creó una vez, por lo que el cierre solo se creó una vez. –

+0

gracias por la aclaración, veo lo que está sucediendo ahora. – Tauren

11

Lo siento por resucitar una vieja pregunta, pero quería añadir algo que he descubierto recientemente en otro lugar aquí en la SO (mirando por el enlace, puede editar/añadir una sola vez lo encuentro) : found it.

Personalmente me gusta la metodología a continuación porque puedo agrupar visualmente todas mis definiciones de prototipo y 'instancia' junto con la definición de la función, evitando evaluarlas más de una vez. También brinda la oportunidad de realizar cierres con sus métodos prototipo, que pueden ser útiles para crear variables 'privadas' compartidas por diferentes métodos de prototipos.

var MyObject = (function() { 
    // Note that this variable can be closured with the 'instance' and prototype methods below 
    var outerScope = function(){}; 

    // This function will ultimately be the "constructor" for your object 
    function MyObject() { 
     var privateVariable = 1; // both of these private vars are really closures specific to each instance 
     var privateFunction = function(){}; 
     this.PublicProtectedFunction = function(){ }; 
    } 

    // "Static" like properties/functions, not specific to each instance but not a prototype either 
    MyObject.Count = 0; 

    // Prototype declarations 
    MyObject.prototype.someFunction = function() { }; 
    MyObject.prototype.someValue = 1; 

    return MyObject; 
})(); 

// note we do automatic evalution of this function, which means the 'instance' and prototype definitions 
// will only be evaluated/defined once. Now, everytime we do the following, we get a new instance 
// as defined by the 'function MyObject' definition inside 

var test = new MyObject(); 
+0

es una idea genial, ¡gracias por compartirla! – Tauren

+0

Eso es exactamente lo que necesitaba, ¡muchas gracias! – seahorsepip

Cuestiones relacionadas