Getters \ setters pour les nuls

133

J'ai essayé de comprendre les getters et les setters et ce n'est pas le cas. J'ai lu JavaScript Getters and Setters et Defining Getters and Setters et je ne les comprends pas.

Quelqu'un peut-il dire clairement:

  1. Ce qu'un getter et un setter sont censés faire, et
  2. Donnez des exemples TRÈS simples?
Alexandre Abakumov
la source
12
Personnellement, je ne sais pas comment vous pouvez obtenir une explication plus claire que celle de John ...
Jason Bunting
Dans le monde OO, getter et setter aident à écrire une classe correctement encapsulée
surexchange
Fondamentalement, je le vois comme ceci: vous définissez des surcharges pour obtenir et définir la propriété, et ces surcharges sont des fonctions; mais vous n'êtes pas obligé de les appeler. De cette façon, vous pouvez remplacer a = setValue(5);par a = 5;et setValue()seriez ainsi appelé sous le capot pour faire ce que vous voulez.
Andrew

Réponses:

102

En plus de la réponse de @ millimoose , les setters peuvent également être utilisés pour mettre à jour d'autres valeurs.

function Name(first, last) {
    this.first = first;
    this.last = last;
}

Name.prototype = {
    get fullName() {
        return this.first + " " + this.last;
    },

    set fullName(name) {
        var names = name.split(" ");
        this.first = names[0];
        this.last = names[1];
    }
};

Maintenant, vous pouvez définir fullNameet firstet lastsera mis à jour et vice versa.

n = new Name('Claude', 'Monet')
n.first # "Claude"
n.last # "Monet"
n.fullName # "Claude Monet"
n.fullName = "Gustav Klimt"
n.first # "Gustav"
n.last # "Klimt"
Matthew Crumley
la source
2
@Akash: Non, cependant, Internet Explorer 9 prend en charge la nouvelle Object.definePropertyfonction qui peut définir les getters et les setters.
Matthew Crumley
9
N'est-ce pas vraiment douloureux que MS ne supporte pas correctement JS et qu'ils ne font pas fonctionner leur silverlight partout, alors je dois tout programmer deux fois, un pour SL et un pour le reste du monde :)
Akash Kava
2
@Martin: Vous pouvez les rendre privés en utilisant la même technique que dans la réponse de John . Si vous souhaitez utiliser de vrais getters / setters, vous devrez utiliser this.__defineGetter__ou la nouvelle Object.definePropertyfonction.
Matthew Crumley
1
Un seul problème avec l'approche répertoriée ci-dessus, si vous souhaitez ajouter des getters et des setters pour une classe déjà existante, il remplacera les prototypes et les méthodes originales ne seront pas accessibles.
xchg.ca
1
Cette approche n'écrase-t-elle pas Name.prototype.constructor? Cela semble être une mauvaise alternative à la réponse de millimoose .
r0estir0bbe
62

Getters et Setters en JavaScript

Aperçu

Les getters et les setters en JavaScript sont utilisés pour définir des propriétés calculées ou des accesseurs . Une propriété calculée est une propriété qui utilise une fonction pour obtenir ou définir une valeur d'objet. La théorie de base fait quelque chose comme ceci:

var user = { /* ... object with getters and setters ... */ };
user.phone = '+1 (123) 456-7890'; // updates a database
console.log( user.areaCode ); // displays '123'
console.log( user.area ); // displays 'Anytown, USA'

Ceci est utile pour faire automatiquement des choses en arrière-plan lors de l'accès à une propriété, comme garder des nombres dans la plage, reformater des chaînes, déclencher des événements de valeur a changé, mettre à jour des données relationnelles, donner accès à des propriétés privées, etc.

Les exemples ci-dessous montrent la syntaxe de base, bien qu'ils obtiennent et définissent simplement la valeur d'objet interne sans rien faire de spécial. Dans des cas réels, vous modifieriez la valeur d'entrée et / ou de sortie en fonction de vos besoins, comme indiqué ci-dessus.

obtenir / définir des mots clés

ECMAScript 5 prend en charge getet setmots - clés pour définir les propriétés calculées. Ils fonctionnent avec tous les navigateurs modernes sauf IE 8 et les versions antérieures.

var foo = {
    bar : 123,
    get bar(){ return bar; },
    set bar( value ){ this.bar = value; }
};
foo.bar = 456;
var gaz = foo.bar;

Getters et Setters personnalisés

getet setne sont pas des mots réservés, ils peuvent donc être surchargés pour créer vos propres fonctions de propriétés personnalisées et calculées entre navigateurs. Cela fonctionnera dans n'importe quel navigateur.

var foo = {
    _bar : 123,
    get : function( name ){ return this[ '_' + name ]; },
    set : function( name, value ){ this[ '_' + name ] = value; }
};
foo.set( 'bar', 456 );
var gaz = foo.get( 'bar' );

Ou pour une approche plus compacte, une seule fonction peut être utilisée.

var foo = {
    _bar : 123,
    value : function( name /*, value */ ){
        if( arguments.length < 2 ){ return this[ '_' + name ]; }
        this[ '_' + name ] = value;
    }
};
foo.value( 'bar', 456 );
var gaz = foo.value( 'bar' );

Évitez de faire quelque chose comme ça, ce qui peut entraîner un gonflement du code.

var foo = {
    _a : 123, _b : 456, _c : 789,
    getA : function(){ return this._a; },
    getB : ..., getC : ..., setA : ..., setB : ..., setC : ...
};

Pour les exemples ci-dessus, les noms de propriétés internes sont abstraits avec un trait de soulignement afin de décourager les utilisateurs de simplement faire foo.barcontre foo.get( 'bar' )et obtenir une valeur "non cuite". Vous pouvez utiliser du code conditionnel pour faire différentes choses en fonction du nom de la propriété en cours d'accès (via le nameparamètre).

Object.defineProperty ()

L'utilisation Object.defineProperty()est une autre façon d'ajouter des getters et des setters, et peut être utilisée sur des objets après leur définition. Il peut également être utilisé pour définir des comportements configurables et énumérables. Cette syntaxe fonctionne également avec IE 8, mais malheureusement uniquement sur les objets DOM.

var foo = { _bar : 123 };
Object.defineProperty( foo, 'bar', {
    get : function(){ return this._bar; },
    set : function( value ){ this._bar = value; }
} );
foo.bar = 456;
var gaz = foo.bar;

__defineGetter __ ()

Enfin, __defineGetter__()est une autre option. Il est obsolète, mais toujours largement utilisé sur le Web et donc peu susceptible de disparaître de sitôt. Il fonctionne sur tous les navigateurs sauf IE 10 et les versions antérieures. Bien que les autres options fonctionnent également bien sur les non-IE, celle-ci n'est donc pas si utile.

var foo = { _bar : 123; }
foo.__defineGetter__( 'bar', function(){ return this._bar; } );
foo.__defineSetter__( 'bar', function( value ){ this._bar = value; } );

Il convient également de noter que dans ces derniers exemples, les noms internes doivent être différents des noms des accesseurs pour éviter la récursivité (c'est-à-dire, foo.barappeler foo.get(bar)appeler foo.barappeler foo.get(bar)...).

Voir également

MDN get , set , Object.defineProperty () , __defineGetter __ () , __defineSetter __ ()
MSDN IE8 Getter Support

Beejor
la source
1
Dans l' approche plus compacte , this[ '_' + name ] = value;pourrait être this[ '_' + name ] = arguments[1];et il n'y aurait pas besoin de spécifier valuearg.
Redhart
1
L'exemple: var foo = { bar : 123, get bar(){ return bar; }, set bar( value ){ this.bar = value; } }; foo.bar = 456; déclenche une exception: Uncaught RangeError: Taille maximale de la pile d'appels dépassée à Object.set bar [as bar] (<anonymous>: 4: 32) at Object.set bar [as bar] (<anonymous>: 4: 32 ) à Object.set bar [as bar] (<anonymous>: 4: 32) at Object.set bar [as bar] (<anonymous>: 4: 32) at Object.set bar [as bar] (<anonymous> : 4: 32) à Object.set bar [as bar] (<anonymous>: 4: 32)
nevf
1
Le nom set / get doit être différent du nom de la propriété. Donc, au lieu de bar: 123et this.bar = valueetc., changez-les _bar par exemple. Voir: hongkiat.com/blog/getters-setters-javascript
nevf
@nevf Merci pour la correction! Oui, généralement avec les propriétés calculées, la "vraie" interne est nommée comme _fooou mFoo. Si c'est le même que le getter / setter, cela provoquera une boucle infinie en raison de la récursivité puis un Stack Overflow ™ ;-) car quand vous dites a = b, il appelle a.get (b) qui lui-même appelle a = b , qui appelle a.get (b), ...
Beejor
58

Vous les utiliseriez par exemple pour implémenter des propriétés calculées.

Par exemple:

function Circle(radius) {
    this.radius = radius;
}

Object.defineProperty(Circle.prototype, 'circumference', {
    get: function() { return 2*Math.PI*this.radius; }
});

Object.defineProperty(Circle.prototype, 'area', {
    get: function() { return Math.PI*this.radius*this.radius; }
});

c = new Circle(10);
console.log(c.area); // Should output 314.159
console.log(c.circumference); // Should output 62.832

(CodePen)

millimoose
la source
Ok, je pense que je commence à comprendre. J'essaye d'attribuer un getter à la propriété length d'un objet de tableau mais j'obtiens une erreur: "Redeclaration of var length" Et le code ressemble à ceci: obj = []; obj .__ defineGetter __ ('length', function () {return this.length;});
1
C'est parce que les objets Array ont déjà une propriété de longueur intégrée. Si la redéclaration était autorisée, l'appel de la nouvelle longueur reviendrait indéfiniment. Essayez d'appeler la propriété "my_length" ou quelque chose du genre.
millimoose
Afin de définir les deux getters dans une seule instruction, utilisez Object.defineProperties.
r0estir0bbe
Vous ne pouvez pas simplement créer {"area": ​​function () {return ...}}? affectez-le simplement comme propriété d'objet
RegarBoy
@developer Ce n'est pas un getter Javascript tel que défini par le langage, c'est juste une fonction. Vous devez l'appeler pour obtenir la valeur, cela ne surcharge pas l'accès à la propriété. Il existe également un cercle d'enfer spécial réservé aux personnes qui inventent leurs propres systèmes d'objets cassés dans JS au lieu de s'appuyer sur celui qu'il possède déjà.
millimoose
16

Désolé de ressusciter une vieille question, mais j'ai pensé que je pourrais apporter quelques exemples très basiques et des explications pour les nuls. Aucune des autres réponses publiées jusqu'ici n'illustre la syntaxe comme le premier exemple du guide MDN , qui est à peu près aussi basique que possible.

Getter:

var settings = {
    firstname: 'John',
    lastname: 'Smith',
    get fullname() { return this.firstname + ' ' + this.lastname; }
};

console.log(settings.fullname);

... se connectera John Smith, bien sûr. Un getter se comporte comme une propriété d'objet variable, mais offre la flexibilité d'une fonction pour calculer sa valeur renvoyée à la volée. C'est fondamentalement un moyen sophistiqué de créer une fonction qui ne nécessite pas () lors de l'appel.

Setter:

var address = {
    set raw(what) {
        var loc = what.split(/\s*;\s*/),
        area = loc[1].split(/,?\s+(\w{2})\s+(?=\d{5})/);

        this.street = loc[0];
        this.city = area[0];
        this.state = area[1];
        this.zip = area[2];
    }
};

address.raw = '123 Lexington Ave; New York NY  10001';
console.log(address.city);

... se connectera New Yorkà la console. Comme les getters, les setters sont appelés avec la même syntaxe que la définition de la valeur d'une propriété d'objet, mais sont encore une autre façon sophistiquée d'appeler une fonction sans ().

Voir ce jsfiddle pour un exemple plus complet, peut-être plus pratique. La transmission de valeurs au setter de l'objet déclenche la création ou le remplissage d'autres éléments d'objet. Plus précisément, dans l'exemple jsfiddle, la transmission d'un tableau de nombres invite le poseur à calculer la moyenne, la médiane, le mode et la plage; définit ensuite les propriétés d'objet pour chaque résultat.

Rojo
la source
Je ne comprends toujours pas l'avantage d'utiliser get and set par rapport à la création d'un getMethod ou setMethod. Est-ce que le seul avantage que vous pouvez appeler sans ()? Il doit y avoir une autre raison pour laquelle il est ajouté à javascript.
Andreas
@Andreas Les getters et les setters se comportent comme des propriétés lorsqu'ils sont appelés, ce qui peut aider à articuler leur objectif. Ils ne débloquent pas de capacités autrement manquantes, mais leur utilisation peut vous aider à organiser vos pensées. C'est le vrai avantage. À titre d'exemple pratique, j'utilisais un getter pour étendre un objet Google Maps. J'avais besoin de calculer l'angle de roulis de la caméra pour pouvoir faire pivoter les tuiles de la carte à plat à l'horizon. Google le fait automatiquement sur le back-end maintenant; mais à l'époque, il m'a été utile de récupérer maps.rollcomme propriété plutôt que comme valeur de maps.roll()retour. C'est juste une préférence.
rojo
c'est donc juste du sucre syntaxique pour rendre le code plus propre sans le (). Je ne vois pas pourquoi vous ne pouvez pas votre exemple avecmaps.roll()
Andreas
@Andreas Qui a dit que je ne pouvais pas? Comme je l'ai dit, c'est juste une façon de m'aider à garder mes pensées organisées. Le codage est un art. Vous ne demandez pas à Bob Ross pourquoi il a dû utiliser l'ocre brûlée alors qu'il aurait pu utiliser de l'orange. Vous ne voyez peut-être pas un besoin maintenant, mais un jour où vous déciderez que votre peinture a besoin d'un peu d'ocre brûlée, elle sera sur votre palette.
rojo
:) une chose que je vois que fait la syntaxe get and set, c'est qu'elle s'exécute automatiquement si elle est utilisée comme propriété d'une propriété.
Andreas
11

Les getters et les setters n'ont vraiment de sens que lorsque vous avez des propriétés privées de classes. Étant donné que Javascript n'a pas vraiment de propriétés de classe privées comme vous le pensez normalement dans les langages orientés objet, cela peut être difficile à comprendre. Voici un exemple d'objet compteur privé. La bonne chose à propos de cet objet est que la variable interne "count" n'est pas accessible depuis l'extérieur de l'objet.

var counter = function() {
    var count = 0;

    this.inc = function() {
        count++;
    };

    this.getCount = function() {
        return count;
    };
};

var i = new Counter();
i.inc();
i.inc();
// writes "2" to the document
document.write( i.getCount());

Si vous êtes toujours confus, jetez un œil à l'article de Crockford sur les membres privés en Javascript .

John
la source
39
Je ne suis pas d'accord. Les getters et setters sont également très utiles pour encapsuler des informations dont la définition peut ne pas être simplement une simple variable. Cela peut être pratique si vous devez modifier le comportement d'un système qui utilisait auparavant des propriétés simples et dont d'autres choses peuvent dépendre. De plus, votre exemple ne montre que des "pseudo getters" qui ne sont que des fonctions. Les getters JavaScript réels apparaissent comme des valeurs simples (et sont accessibles sans notation de fonction), d'où leur véritable puissance.
devios1
Je ne sais pas si j'appellerais cela puissant. Quelque chose apparaissant comme X mais qui est vraiment Y n'est pas nécessairement clair. Je ne m'attendrais absolument PAS var baz = foo.barà avoir un ensemble complet de comportements cachés derrière. Je voudrais attendre à ce que de foo.getBar()cependant.
AgmLauncher
8

Je pense que le premier article auquel vous créez un lien le dit assez clairement:

L'avantage évident d'écrire du JavaScript de cette manière est que vous pouvez utiliser des valeurs obscures auxquelles vous ne voulez pas que l'utilisateur accède directement.

Le but ici est d'encapsuler et de faire abstraction des champs en ne leur permettant d'accéder qu'à une méthode get()ou set(). De cette façon, vous pouvez stocker le champ / les données en interne comme vous le souhaitez, mais les composants extérieurs ne sont que loin de votre interface publiée. Cela vous permet d'effectuer des modifications internes sans modifier les interfaces externes, de faire une validation ou une vérification des erreurs dans la set()méthode, etc.

mat b
la source
6

Bien que nous soyons souvent habitués à voir des objets avec des propriétés publiques sans aucun contrôle d'accès, JavaScript nous permet de décrire avec précision les propriétés. En fait, nous pouvons utiliser des descripteurs pour contrôler comment une propriété est accessible et quelle logique nous pouvons lui appliquer. Prenons l'exemple suivant:

var employee = {
    first: "Boris",
    last: "Sergeev",
    get fullName() {
        return this.first + " " + this.last;
    },
    set fullName(value) {
        var parts = value.toString().split(" ");
        this.first = parts[0] || "";
        this.last = parts[1] || "";
    },
    email: "[email protected]"
};

Le résultat final:

console.log(employee.fullName); //Boris Sergeev
employee.fullName = "Alex Makarenko";

console.log(employee.first);//Alex
console.log(employee.last);//Makarenko
console.log(employee.fullName);//Alex Makarenko
Michael Horojanski
la source
2

Ce qui est si déroutant à ce sujet ... les getters sont des fonctions qui sont appelées lorsque vous obtenez une propriété, des setters, lorsque vous la définissez. exemple, si vous faites

obj.prop = "abc";

Vous définissez la propriété prop, si vous utilisez des getters / setters, alors la fonction setter sera appelée, avec "abc" comme argument. La définition de la fonction setter à l'intérieur de l'objet ressemblerait idéalement à quelque chose comme ceci:

set prop(var) {
   // do stuff with var...
}

Je ne sais pas si cela est bien implémenté dans les navigateurs. Il semble que Firefox a également une syntaxe alternative, avec des méthodes spéciales («magiques») à double soulignement. Comme d'habitude, Internet Explorer ne prend en charge rien de tout cela.

Rolf
la source
2

Vous pouvez définir une méthode d'instance pour la classe js, via le prototype du constructeur.

Voici l'exemple de code:

// BaseClass

var BaseClass = function(name) {
    // instance property
    this.name = name;
};

// instance method
BaseClass.prototype.getName = function() {
    return this.name;
};
BaseClass.prototype.setName = function(name) {
    return this.name = name;
};


// test - start
function test() {
    var b1 = new BaseClass("b1");
    var b2 = new BaseClass("b2");
    console.log(b1.getName());
    console.log(b2.getName());

    b1.setName("b1_new");
    console.log(b1.getName());
    console.log(b2.getName());
}

test();
// test - end

Et, cela devrait fonctionner pour n'importe quel navigateur, vous pouvez également simplement utiliser nodejs pour exécuter ce code.

Eric Wang
la source
1
Il s'agit simplement de créer de nouvelles méthodes getName et setName. Ceux-ci ne sont pas liés à la création de propriété!
uzay95
2

J'ai également été quelque peu déconcerté par l' explication que j'ai lue , car j'essayais d'ajouter une propriété à un prototype existant que je n'avais pas écrit, alors le remplacement du prototype semblait être la mauvaise approche. Donc, pour la postérité, voici comment j'ai ajouté une lastpropriété à Array:

Object.defineProperty(Array.prototype, "last", {
    get: function() { return this[this.length - 1] }
});

Jamais si légèrement plus agréable que d'ajouter une fonction IMHO.

Shawkinaw
la source
1

Si vous faites référence au concept des accesseurs, le but simple est de masquer le stockage sous-jacent de toute manipulation arbitraire. Le mécanisme le plus extrême pour cela est

function Foo(someValue) {
    this.getValue = function() { return someValue; }
    return this;
}

var myFoo = new Foo(5);
/* We can read someValue through getValue(), but there is no mechanism
 * to modify it -- hurrah, we have achieved encapsulation!
 */
myFoo.getValue();

Si vous faites référence à la fonctionnalité réelle de lecture / définition JS, par exemple. defineGetter/ defineSetter, ou { get Foo() { /* code */ } }alors il convient de noter que dans la plupart des moteurs modernes, l'utilisation ultérieure de ces propriétés sera beaucoup plus lente qu'elle ne le serait autrement. par exemple. comparer les performances de

var a = { getValue: function(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.getValue();

contre.

var a = { get value(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.value;
olliej
la source
-1

J'en ai un pour vous les gars qui est peut-être un peu moche, mais cela se fait sur toutes les plateformes

function myFunc () {

var _myAttribute = "default";

this.myAttribute = function() {
    if (arguments.length > 0) _myAttribute = arguments[0];
    return _myAttribute;
}
}

de cette façon, quand tu appelles

var test = new myFunc();
test.myAttribute(); //-> "default"
test.myAttribute("ok"); //-> "ok"
test.myAttribute(); //-> "ok"

Si vous voulez vraiment pimenter les choses ... vous pouvez insérer un type de chèque:

if (arguments.length > 0 && typeof arguments[0] == "boolean") _myAttribute = arguments[0];
if (arguments.length > 0 && typeof arguments[0] == "number") _myAttribute = arguments[0];
if (arguments.length > 0 && typeof arguments[0] == "string") _myAttribute = arguments[0];

ou devenez encore plus fou avec le code avancé de typeof check: type.of () sur codingforums.com

artsy.ca
la source
le but était de pouvoir changer un attribut en quelque chose de plus sophistiqué sans avoir besoin de changer l'interface publique. L'ajout d'une balise call () le change.
Michael Scott Cuthbert