web-dev-qa-db-fra.com

JS defineProperty et prototype

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
            }
        })
    })
};
34
nicholas_r

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:

  • Avec ma méthode: vous n'aurez que 2 fonctions en mémoire;
  • Avec les autres méthodes: 10000 * 2 = 20000 fonctions en mémoire;
50
Totty.js

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/

18
HBP

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;
})();
8
Mikhail Kraynov

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;
            }
        });
    });
};
5
Bergi

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
1
Andrii Muzalevskyi

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.

0
Hai Phan