web-dev-qa-db-fra.com

Getter / setter dans le constructeur

J'ai récemment lu le fait qu'il est possible de définir des getters/setters dans JavaScript. Il semble extrêmement utile - le setter est une sorte de "assistant" qui peut analyser la valeur à définir en premier, avant de la définir.

Par exemple, j'ai actuellement ce code:

var obj = function(value) {
    var test = !!value; // 'test' has to be a boolean
    return {
        get test() { return test },
        set test(value) { test = !!value }
    };
};

var instance = new obj(true);

Ce code convertit toujours value à un booléen. Donc, si vous codez instance.test = 0, alors instance.test === false.

Cependant, pour que cela fonctionne, vous devez réellement retourner un objet, ce qui signifie que la nouvelle instance n'est pas de type obj mais est un objet simple. Cela signifie que la modification du prototype de obj n'a aucun effet sur les instances. Par exemple, cela fait pas Travail - instance.func est indéfini:

obj.prototype.func = function() { console.log(this.value); };

parce que instance n'est pas de type obj. Pour que les fonctions de prototype fonctionnent, je suppose que je ne devrais pas retourner un objet clair, mais je ne retourne rien de pour que instance serait de type obj, comme un constructeur ordinaire.

Le problème est alors comment mettre en œuvre des getters/setters? Je ne peux trouver que des articles décrivant comment les ajouter à un objet et non comme faisant partie du constructeur d'un type personnalisé.

Comment puis-je implémenter les getters/setters dans le constructeur de manière à pouvoir utiliser les getters/setters et prolonger le prototype?

32
pimvdb

Vous ne pouvez pas faire ça.

Vous pouvez définir un réglage/getters pour des propriétés d'objets cependant. Je conseille que vous utilisez es5 Object.defineProperties cependant. Bien sûr, cela ne fonctionne que dans les navigateurs modernes.

var obj = function() {
    ...
    Object.defineProperties(this, {
        "test": {
             "get": function() { ... },
             "set": function() { ... }
        }
    });
}

obj.prototype.func = function() { ... }

var o = new obj;
o.test;
o.func();
47
Raynos

Habituellement, vous voulez classe Méthodes. La réponse de @raynos le 7 mai 2011 obtient le travail effectué, mais il définit un instance méthode, pas une méthode de classe.

Ce qui suit illustre une définition de classe avec un getter et un setter faisant partie de la classe. Cette définition ressemble beaucoup à la réponse de @raynos, mais avec deux différences dans le code: (1) l'action "DefineProperties ()" a été déplacée du constructeur. (2) L'argument de "définir des propres ()" comme évolua de l'objet d'instance "Ceci", à l'objet prototype du constructeur.

function TheConstructor(side) {
  this.side = side;
}

Object.defineProperties(TheConstructor.prototype, {
        area: {
             get: function()    { return this.side * this.side; }
            ,set: function(val) { this.side = Math.sqrt(val);   }
        }
});

// Test code:

var anInstance = new TheConstructor(2);
console.log("initial  Area:"+anInstance.area);
anInstance.area = 9;
console.log("modified Area:"+anInstance.area);

Qui produit ces résultats:

initial  Area:4
modified Area:9

Bien que la distinction entre la classe par rapport à la définition des instances est juste une question de style, il existe un objet de bon style, et il existe un cas où la distinction compte: le getter mémoisé. Le but d'un getter mémo est décrit ici: Smart/auto-écrasement/getters paresseux

Définissez le getter au niveau de la classe lorsque la valeur commuée est de relever la classe entière. Par exemple, un fichier de configuration ne doit être lu qu'une seule fois; Les valeurs résultantes doivent ensuite s'appliquer pendant la durée du programme. Le code d'échantillon suivant définit un getter mémo à la classe.

function configureMe() {
  return 42;
}

Object.defineProperties(TheConstructor.prototype, {
    memoizedConfigParam: {
        get: function() {
            delete TheConstructor.prototype.memoizedConfigParam;
            return TheConstructor.prototype.memoizedConfigParam = configureMe();
        }
        ,configurable:  true
    }
});

// Test code:

console.log("memoizedConfigParam:"+anInstance.memoizedConfigParam);

Produit:

memoizedConfigParam:42

Comme on peut le voir dans l'exemple, les getteurs mémo ont la caractéristique que la fonction getter se supprime elle-même, puis remplace elle-même une valeur simple qui ne changera jamais. Notez que 'configurable' doit être réglé sur "vrai".

Définissez le getter au niveau de l'instance lorsque la valeur commuée dépend du contenu de l'instance. La définition se déplace à l'intérieur du constructeur et l'objet de l'attention est "Ceci".

function TheConstructorI(side) {

  this.side = side;

  Object.defineProperties(this, {
    memoizedCalculation: {
        get: function() {
            delete this.memoizedCalculation;
            return this.memoizedCalculation = this.expensiveOperation();
        }
        ,configurable:  true
    }
  });
}

TheConstructorI.prototype.expensiveOperation = function() {
  return this.side * this.side * this.side;
}

//Test code:

var instance2 = new TheConstructorI(2);
var instance3 = new TheConstructorI(3);

console.log("memoizedCalculation 2:"+instance2.memoizedCalculation);
console.log("memoizedCalculation 3:"+instance3.memoizedCalculation);

Produit:

memoizedCalculation 2:8
memoizedCalculation 3:27

Si vous souhaitez garantir (plutôt que présumer) que la valeur commémorée ne sera jamais modifiée, l'attribut "Entrescriptions" doit être modifié. Cela rend le code un peu plus compliqué.

function TheConstructorJ(side) {

  this.side = side;

  Object.defineProperties(this, {
    memoizedCalculation: {
        get: function() {
            delete this.memoizedCalculation;
            Object.defineProperty( this, 'memoizedCalculation'
              ,{  value    : this.expensiveOperation()
                 ,writable : false
              });
            return this.memoizedCalculation;
        }
        ,configurable:  true
    }
  });
}

TheConstructorJ.prototype.expensiveOperation = function() {
  return this.side * this.side * this.side;
}

//Test code:

var instanceJ = new TheConstructorJ(2);

console.log("memoizedCalculation:"+instanceJ.memoizedCalculation);
instanceJ.memoizedCalculation = 42;  // results in error

Produit:

memoizedCalculation:8
>Uncaught TypeError: Cannot assign to read only property 'memoizedCalculation' of object '#<TheConstructorJ>'

La question originale de l'OP, à partir du 7 mars 2011, a présenté la syntaxe de base Getter and Setter, a noté qu'il a fonctionné sur un objet mais non sur "Ceci", et a demandé comment définir les getters et les configurateurs dans un constructeur. En plus de tous les exemples ci-dessus, il existe également une façon de le faire: créer un nouvel objet dans le constructeur, comme l'OP, mais attribue ensuite l'objet à être membre dans "Ceci". Donc, le code d'origine ressemblerait à ceci:

var MyClass = function(value) {
    var test = !!value; // 'test' has to be a boolean
    this.data = {
        get test() { return test },
        set test(value) { test = !!value }
    };
};

var instance = new MyClass(true);

// But now 'data' is part of the access path
instance.data.test = 0;
console.log(instance.data.test);

Produit:

false

Croyez-le ou non, j'ai réellement rencontré des situations où ce "coup de vitesse" est la meilleure solution. Spécifiquement, j'ai utilisé cette technique lorsque j'avais des enregistrements de plusieurs tables encapsulées dans une seule classe et souhaitait présenter une vue unifiée comme s'il s'agissait d'un seul disque appelé "données".

S'amuser.

Iam_al_x

10
IAM_AL_X

MISE À JOUR POUR ES6 - Jetez un coup d'œil à la section 19.3.1 du livre de Alex Rauschmayer Explorer ES6http://exploringjs.com/es6/ch_maps-sets.htm#sec_weakmaps- Données privées qui démontre comment utiliser des faibles applications avec getters et setters pour contenir des données privées. Combinaison avec la section 16.2.2.3 http://exploringjs.com/es6/ch_classes.html#leanpub-auto-getters-andingterters entraînerait quelque chose comme

# module test_WeakMap_getter.js
var _MyClassProp = new WeakMap();
class MyClass {
    get prop() {
        return _MyClassProp.get( this ); 
    }
    set prop(value) {
        _MyClassProp.set( this, value );
    }
}
var mc = new MyClass();
mc.prop = 5 ;
console.log( 'My value is', mc.prop );

$ node --use_strict test_WeakMap_getter.js 
My value is 5
7
Jeff
function Obj(value){
    this.value = !!value;
}

Obj.prototype = {
    get test () {
        return this.value;``
    },
    set test (value) {
        this.value = !!this.value;
    }
};
var obj = new Obj(true);
4
Umit Silwal Khatri

@Alex Je le vois comme plus d'option et plus de puissance, la programmation est de l'art, @nat partage sa découverte avec nous, et pour cela, je le remercie. Peut-être que quelqu'un veut le faire de cette façon.

Je suis sûr que la version du setter est la même mais simplement en modifiant que g to a s.

i.g:

function Constructor(input){
     this.input = input;
}

Object.__defineGetter__.call(Constructor.prototype, "value", function(){
    return this.input * 2;
});

Object.__defineSetter__.call(Constructor.prototype, "bar", function(foo){
    return this.input *= foo;
});

var test = new Constructor(5);
console.log(test.value); // 10
test.bar = 5;
console.log(test.input); //25

Avec cela, cette caractéristique est obsolète, des conseils de ne pas utiliser dans le codage de la production.

1
user3552042

Je sais que cela pourrait être extrêmement tard, mais j'ai compris une autre façon d'accomplir ce que vous voulez et pour le bien des gens, comme moi, googling pour une réponse à cela ici.

function Constructor(input){
     this.input = input;
}
Object.__defineGetter__.call(Constructor.prototype, "value", function(){
    return this.input * 2;
});

var test = new Constructor(5);
alert(test.value) // 10

J'ai testé cela dans Chrome, Safari, Mobile Safari, Firefox et ils fonctionnent tous (dernières versions bien sûr)

1
Akinos