tipos - type of javascript
Javascript getter/setters automático(John Resig Book) (8)
Estoy leyendo " Pro Javascript Techniques " de John Resig, y estoy confundido con un ejemplo. Este es el código:
// Create a new user object that accepts an object of properties
function User( properties ) {
// Iterate through the properties of the object, and make sure
// that it''s properly scoped (as discussed previously)
for ( var i in properties ) { (function(){
// Create a new getter for the property
this[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
this[ "set" + i ] = function(val) {
properties[i] = val;
};
})(); }
}
// Create a new user object instance and pass in an object of
// properties to seed it with
var user = new User({
name: "Bob",
age: 44
});
// Just note that the name property does not exist, as it''s private
// within the properties object
alert( user.name == null );
// However, we''re able to access its value using the new getname()
// method, that was dynamically generated
alert( user.getname() == "Bob" );
// Finally, we can see that it''s possible to set and get the age using
// the newly generated functions
user.setage( 22 );
alert( user.getage() == 22 );
Ahora ejecutando eso en la consola Firebug (en FF3) lanza ese usuario.getname () no es una función. Intenté hacer esto:
var other = User
other()
window.getname() --> this works!
¡Y funcionó!
¿Alguna idea de por qué? ¡gracias a todos!
PD: Recomiendo encarecidamente este libro.
EDITAR:
obra:
var me = this;
parece funcionar un poco mejor, pero al ejecutar "getname ()" devuelve ''44'' (la segunda propiedad) ...
también me parece extraño que funcionó en el objeto ventana sin modificación ...
y una tercera pregunta, ¿cuál es la diferencia entre la solución PEZ y el original? (él no usa una función anónima)
¡Gracias a todos por los comentarios! +1
¿Tal vez la variable i está "cerrada" con el último valor en la iteración ("edad")? Entonces todos los getters y setters accederán a propiedades ["age"].
Como está escrito en el OP, this
en el ciclo no se refiere al objeto User como debería ser. Si captura esa variable fuera del ciclo, puede hacer que funcione:
function User( properties ) {
// Iterate through the properties of the object, and make sure
// that it''s properly scoped (as discussed previously)
var me = this;
for ( i in properties ) { (function(){
// Create a new getter for the property
me[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
me[ "set" + i ] = function(val) {
properties[i] = val;
};
// etc
Encontré algo que parece ser la respuesta, se trata de contexto. Usando la función anónima dentro de for, cambia el contexto haciendo que ''this'' se refiera al objeto window, ¿no es así?
asi que:
function User( properties ) {
for ( var i in properties ) {
// here this == User Object
(function(){
// inside this anonymous function this == window object
this[ "get" + i ] = function() {
return properties[i];
};
this[ "set" + i ] = function(val) {
properties[i] = val;
};
})();
}
}
No sé por qué esa función cambia el contexto de ejecución, no estoy seguro de que deba hacer eso, de todos modos puedes probarlo ejecutando el código allí y probando window.getname () ¡y mágicamente funciona! : S
La solución tan saciada antes está cambiando el contexto, se puede hacer como dijo J Cooper, pasando la variable "yo" y haciendo que la función sea un cierre o puede hacer esto:
(function(){
// inside this anonymous function this == User because we called it with ''call''
this[ "get" + i ] = function() {
return properties[i];
};
this[ "set" + i ] = function(val) {
properties[i] = val;
};
}).call(this);
De todos modos, todavía tengo 44 cuando ejecuto ''getname'' ... alguna idea?
es probable que desee algo como esto, que es más legible: (los cierres son fáciles de aprender una vez que obtiene un poco de práctica)
function User( properties ) {
// helper function to create closures based on passed-in arguments:
var bindGetterSetter = function(obj,p,properties)
{
obj["get"+p]=function() { return properties[p]; }
obj["set"+p]=function(val) { properties[p]=val; return this; }
};
for (var p in properties)
bindGetterSetter(this, p, properties);
}
También agregué "devuelve esto"; para que puedas hacer:
u=new User({a: 1, b:77, c:48});
u.seta(3).setb(20).setc(400)
Comencé este post con el único propósito de saber por qué sucedieron esas cosas, y finalmente lo hice. Entonces, en caso de que haya alguien más interesado en los "por qué" aquí están:
¿Por qué ''esto'' cambia dentro de la función anónima?
Una nueva función, incluso si es anónima, declarada dentro de un objeto u otra función SIEMPRE CAMBIA EL ALCANCE, en este caso volviendo al alcance global (ventana)
Solución: todo lo indicado en la publicación, creo que el más claro es ejecutar la función anónima con .call (esto)
¿Por qué getname () siempre devuelve la edad?
Mientras que la función anónima se ejecuta de inmediato, los getters / setters se ejecutan por primera vez cuando son llamados. En ese momento, el valor de i siempre será el último porque ya se ha iterado para todas las propiedades ... y siempre devolverá las propiedades [i] que es el último valor, en este caso la edad.
Solución: guarde el valor i en una variable como esta
for ( i in properties ) { (function(){
var j = i
//from now on use properties[j]
Eso es básicamente todo, si me equivoco en algo que dije, por favor corrígeme, ya que estoy tratando de aprender esto ...
Gracias de nuevo.
EDITAR: ahora, al adaptar la respuesta de Jason, funciona:
Necesitamos hacer un cierre para los valores. Aquí hay una forma:
function bindAccessors(o, property, value) {
var _value = value;
o["get" + property] = function() {
return _value;
};
o["set" + property] = function(v) {
_value = v;
};
}
Entonces el constructor del usuario se ve así:
function User( properties ) {
for (var i in properties ) {
bindAccessors(this, i, properties[i]);
}
}
Creo que es mejor no usar la new
palabra clave cuando trabaje en JavaScript.
Esto se debe a que si crea una instancia del objeto sin utilizar la palabra clave nueva (por ejemplo, var user = User()
) por error, * sucederán cosas muy malas ... * razón por la cual en la función (si se crea una instancia sin la palabra clave new
), this
se referirá al objeto global , es decir, la window
...
Por lo tanto, sugiero una forma mejor de cómo usar objetos de clase.
Considere el siguiente ejemplo:
var user = function (props) {
var pObject = {};
for (p in props) {
(function (pc) {
pObject[''set'' + pc] = function (v) {
props[pc] = v;
return pObject;
}
pObject[''get'' + pc] = function () {
return props[pc];
}
})(p);
}
return pObject;
}
En el ejemplo anterior, estoy creando un nuevo objeto dentro de la función y luego adjunto getters y setters a este objeto recién creado.
Finalmente, estoy devolviendo este objeto recién creado. Tenga en cuenta que la palabra clave this
no se usa en ningún lado
Luego, para ''crear una instancia'' de un user
, yo haría lo siguiente:
var john = user({name : ''Andreas'', age : 21});
john.getname(); //returns ''Andreas''
john.setage(19).getage(); //returns 19
La mejor manera de evitar caer en trampas es no crearlas en primer lugar ... En el ejemplo anterior, estoy evitando la new
trampa de palabra clave ( como dije, no usar la palabra clave new
cuando se supone que se utilizará causará cosas malas que suceden ) al no usar nada new
.
Acabo de modificar el código un poco así ... Este debería funcionar ... Esto es lo mismo que establecerme = esto; Pero se requiere un cierre para establecer correctamente el valor de cada propiedad, de lo contrario, el último valor se asignará a todas las propiedades.
// Create a new user object that accepts an object of properties
var User = function( properties ) {
// Iterate through the properties of the object, and make sure
// that it''s properly scoped (as discussed previously)
var THIS = this;
for ( var i in properties ) { (function(i){
// Create a new getter for the property
THIS[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
THIS[ "set" + i ] = function(val) {
properties[i] = val;
};
})(i); }
}
// Create a new user object instance and pass in an object of
// properties to seed it with
var user = new User({
name: "Bob",
age: 44
});
// Just note that the name property does not exist, as it''s private
// within the properties object
alert( user.name == null );
// However, we''re able to access its value using the new getname()
// method, that was dynamically generated
alert( user.getname() == "Bob" );
// Finally, we can see that it''s possible to set and get the age using
// the newly generated functions
user.setage( 22 );
alert( user.getage() == 22 );