Comme vous le savez, nous pouvons définir des getters et des setters dans JS en utilisant defineProperty()
. J'ai été bloqué en essayant d'étendre ma classe en utilisant defineProperty()
.
Voici un exemple de code:
J'ai un tableau de champs qui doivent être ajoutés à un objet
fields = ["id", "name", "last_login"]
J'ai aussi une classe qui sera modifiée
var User = (function(){
// constructor
function User(id, name){
this.id = id
this.name = name
}
return User;
})();
Et une fonction qui ajoutera des champs à la classe en utilisant 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
}
})
})
};
Après avoir exécuté define_fields()
J'ai mes champs dans l'instance de User
define_fields(fields);
user1 = new User(1, "Thomas")
user2 = new User(2, "John")
Mais les valeurs de ces propriétés sont identiques
console.log(user2.id, user2.name) // 2, John
console.log(user1.id, user1.name) // 2, John
Existe-t-il un moyen de faire fonctionner correctement defineProperty()
dans ce cas? Si je comprends bien, le problème est avec value
qui devient identique pour chaque instance de la classe, mais je ne sais pas comment y remédier. Merci d'avance pour vos réponses.
PD: De cette façon, "RangeError: la taille maximale de la pile d'appels est dépassée"
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
}
})
})
};
Veuillez ne pas implémenter une autre version car elle consommera toute votre mémoire dans votre application:
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 vous installez 10000 objets:
Je suis arrivé à la même conclusion que Mikhail Kraynov trois minutes après avoir répondu. Cette solution définit de nouvelles propriétés à chaque appel du constructeur. Je me demandais si, comme vous l'avez demandé, il y avait un moyen de mettre les getters et setters dans le prototype. Voici ce que j'ai trouvé:
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;
}) ();
Dans cette solution, je définis les getters et setters de champ dans le prototype mais référence une propriété (cachée) dans chaque instance qui contient les valeurs de champ.
Voir le violon ici: http://jsfiddle.net/Ca7yq
J'ai ajouté un peu plus de code au violon pour montrer quelques effets sur l'énumération des propriétés: http://jsfiddle.net/Ca7yq/1/
Il me semble que lorsque vous définissez des propriétés pour le prototype, toutes les instances partagent ces propriétés. Donc, la bonne variante pourrait être
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;
})();
Lorsque vous définissez vos propriétés sur l'objet prototype de toutes les instances utilisateur, tous ces objets partagent la même variable value
. Si ce n'est pas ce que vous voulez, vous devrez appeler defineFields
sur chaque instance d'utilisateur séparément - dans le constructeur:
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;
}
});
});
};
Cette solution est sans consommation de mémoire supplémentaire. Votre code mis à jour est proche. Il vous suffit d'utiliser this.props [field_name] au lieu de diriger ce [field_name].
Veuillez noter que l'appel defineProperty a été remplacé par 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
D'après la réponse acceptée, je me rends compte que ce que nous essayons de faire ici est de définir variables d'instance privées. Ces variables doivent se trouver sur l'instance (ceci), plutôt que sur l'objet prototype. Normalement, nous nommons des variables privées en préfixant un trait de soulignement au nom de la propriété.
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 réponse acceptée place à la place toutes les variables privées dans un conteneur, ce qui est en fait mieux.