javascript - propiedad - JS defineProperty y prototipo
object assign (6)
Como saben, podemos definir captadores y definidores en JS utilizando defineProperty()
. Me he quedado atascado al intentar extender mi clase usando defineProperty()
.
Aquí hay un código de ejemplo:
Tengo una matriz de campos que se deben agregar a un objeto
fields = ["id", "name", "last_login"]
También tengo una clase que será modificada.
var User = (function(){
// constructor
function User(id, name){
this.id = id
this.name = name
}
return User;
})();
Y una función que agregará campos a la clase usando defineProperty()
var define_fields = function (fields){
fields.forEach(function(field_name){
var value = null
Object.defineProperty(User.prototype, field_name, {
get: function(){ return value }
set: function(new_value){
/* some business logic goes here */
value = new_value
}
})
})
};
Después de ejecutar define_fields()
tengo mis campos en la instancia del User
define_fields(fields);
user1 = new User(1, "Thomas")
user2 = new User(2, "John")
Pero los valores de estas propiedades son idénticos.
console.log(user2.id, user2.name) // 2, John
console.log(user1.id, user1.name) // 2, John
¿Hay alguna manera de hacer que defineProperty()
funcione correctamente en este caso? Si entiendo que el problema tiene un value
que se vuelve idéntico para cada instancia de la clase, pero no me doy cuenta de cómo solucionarlo. Gracias de antemano por sus respuestas.
UPD: De esta manera se lanza "RangeError: Se excedió el tamaño máximo de pila de llamadas"
var define_fields = function (fields){
fields.forEach(function(field_name){
Object.defineProperty(User.prototype, field_name, {
get: function(){ return this[field_name] }
set: function(new_value){
/* some business logic goes here */
this[field_name] = new_value
}
})
})
};
A medida que defina sus propiedades en el objeto prototipo de todas las instancias de usuario, todos esos objetos compartirán la misma variable de value
. Si eso no es lo que desea, deberá llamar a defineFields
en cada instancia de usuario por separado, en el constructor:
function User(id, name){
this.define_fields(["name", "id"]);
this.id = id
this.name = name
}
User.prototype.define_fields = function(fields) {
var user = this;
fields.forEach(function(field_name) {
var value;
Object.defineProperty(user, field_name, {
get: function(){ return value; },
set: function(new_value){
/* some business logic goes here */
value = new_value;
}
});
});
};
De la respuesta aceptada, me doy cuenta de que lo que intentamos hacer aquí es definir variables de instancia privadas . Estas variables deben estar en la instancia (esto), en lugar de en el objeto prototipo. Normalmente nombramos variables privadas prefijando un guión bajo al nombre de la propiedad.
var Vehicle = {};
Object.defineProperty(Vehicle, "make", {
get: function() { return this._make; }
set: function(value) { this._make = value; }
});
function Car(m) { this.make = m; } //this will set the private var _make
Car.prototype = Vehicle;
La respuesta aceptada básicamente coloca todas las variables privadas en un contenedor, lo que en realidad es mejor.
Esta solución es sin consumo extra de memoria. Su código actualizado está cerca. Solo necesita usar this.props [field_name] en lugar de dirigir este [field_name].
Tenga en cuenta que la llamada defineProperty se reemplazó a Object.create
Js Fiddle http://jsfiddle.net/amuzalevskyi/65hnpad8/
// util
function createFieldDeclaration(fields) {
var decl = {};
for (var i = 0; i < fields.length; i++) {
(function(fieldName) {
decl[fieldName] = {
get: function () {
return this.props[fieldName];
},
set: function (value) {
this.props[fieldName] = value;
}
}
})(fields[i]);
}
return decl;
}
// class definition
function User(id, name) {
this.props = {};
this.id = id;
this.name = name;
}
User.prototype = Object.create(Object.prototype, createFieldDeclaration([''id'',''name'']));
// tests
var Alex = new User(0, ''Alex''),
Andrey = new User(1, ''Andrey'');
document.write(Alex.name + ''<br/>''); // Alex
document.write(Andrey.name + ''<br/>''); // Andrey
Alex.name = "Alexander";
document.write(Alex.name + ''<br/>''); // Alexander
document.write(Andrey.name + ''<br/>''); //Andrey
Llegué a la misma conclusión que Mikhail Kraynov tres minutos después de que respondiera. Esa solución define nuevas propiedades cada vez que se llama al constructor. Me pregunté si, como preguntaste, había una manera de poner a los captadores y establecedores en el prototipo. Aquí es lo que se me ocurrió:
var User = (function () {
function User (id, nam) {
Object.defineProperty (this, ''__'', // Define property for field values
{ value: {} });
this.id = id;
this.nam = nam;
}
(function define_fields (fields){
fields.forEach (function (field_name) {
Object.defineProperty (User.prototype, field_name, {
get: function () { return this.__ [field_name]; },
set: function (new_value) {
// some business logic goes here
this.__[field_name] = new_value;
}
});
});
}) (fields);
return User;
}) ();
En esta solución, defino los captadores y definidores de campo en el prototipo, pero hago referencia a una propiedad (oculta) en cada instancia que contiene los valores de campo.
Vea el violín aquí: http://jsfiddle.net/Ca7yq
Agregué un poco más de código al violín para mostrar algunos efectos en la enumeración de propiedades: http://jsfiddle.net/Ca7yq/1/
Me parece que cuando se definen propiedades para el prototipo, todas las instancias comparten esas propiedades. Así que la variante correcta podría ser
var User = (function(){
// constructor
function User(id, name){
this.id = id
this.name = name
Object.defineProperty(this, "name", {
get: function(){ return name },
set: function(new_value){
//Some business logic, upperCase, for example
new_value = new_value.toUpperCase();
name = new_value
}
})
}
return User;
})();
Por favor, no implemente ninguna otra versión, ya que consumirá toda su memoria en su aplicación:
var Player = function(){this.__gold = 0};
Player.prototype = {
get gold(){
return this.__gold * 2;
},
set gold(gold){
this.__gold = gold;
},
};
var p = new Player();
p.gold = 2;
alert(p.gold); // 4
Si instancia 10000 objetos:
- Con mi método: solo tendrás 2 funciones en la memoria;
- Con los otros métodos: 10000 * 2 = 20000 funciones en la memoria;