186 votes

comment utiliser javascript Object.defineProperty

J'ai cherché comment utiliser le Object.defineProperty mais je n'ai rien trouvé de décent.

Quelqu'un m'a donné ce bout de code :

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
})

Mais je ne le comprends pas. Principalement, le get c'est ce que je n'arrive pas à comprendre (jeu de mots). Comment cela fonctionne-t-il ?

1 votes

522voto

Jan Turoň Points 6598

Puisque vous avez demandé un question similaire prenons les choses en main, étape par étape. C'est un peu plus long, mais cela peut vous faire gagner beaucoup plus de temps que celui que j'ai passé à écrire ceci :

Propriété est une fonctionnalité de la POO conçue pour une séparation nette du code client. Par exemple, dans une boutique en ligne, vous pouvez avoir des objets comme celui-ci :

function Product(name,price) {
  this.name = name;
  this.price = price;
  this.discount = 0;
}

var sneakers = new Product("Sneakers",20); // {name:"Sneakers",price:20,discount:0}
var tshirt = new Product("T-shirt",10);  // {name:"T-shirt",price:10,discount:0}

Ensuite, dans votre code client (la boutique électronique), vous pouvez ajouter des remises à vos produits :

function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }

Plus tard, le propriétaire de la boutique en ligne peut se rendre compte que la remise ne peut pas être supérieure à 80 %, par exemple. Maintenant, vous devez trouver CHAQUE occurrence de la modification de la remise dans le code client et ajouter une ligne

if(obj.discount>80) obj.discount = 80;

Ensuite, le propriétaire de la boutique en ligne peut changer sa stratégie, comme par exemple "si le client est un revendeur, la remise maximale peut être de 90%" . Et vous devez à nouveau effectuer le changement à plusieurs endroits, sans oublier de modifier ces lignes à chaque fois que la stratégie est modifiée. C'est une mauvaise conception. C'est pourquoi encapsulation est le principe de base de la POO. Si le constructeur était comme ceci :

function Product(name,price) {
  var _name=name, _price=price, _discount=0;
  this.getName = function() { return _name; }
  this.setName = function(value) { _name = value; }
  this.getPrice = function() { return _price; }
  this.setPrice = function(value) { _price = value; }
  this.getDiscount = function() { return _discount; }
  this.setDiscount = function(value) { _discount = value; } 
}

Vous pouvez alors simplement modifier le getDiscount ( accesseur ) et setDiscount ( mutateur ). Le problème est que la plupart des membres se comportent comme des variables communes, seule la remise nécessite une attention particulière ici. Mais une bonne conception exige l'encapsulation de chaque membre des données pour que le code reste extensible. Vous devez donc ajouter beaucoup de code qui ne fait rien. C'est également une mauvaise conception, un anti-modèle passe-partout . Parfois, il n'est pas possible de transformer les champs en méthodes par la suite (le code de la boutique en ligne peut devenir volumineux ou le code d'un tiers peut dépendre de l'ancienne version), et le modèle standard est donc un moindre mal dans ce cas. Mais c'est tout de même un mal. C'est pourquoi les propriétés ont été introduites dans de nombreux langages. Vous pouvez conserver le code d'origine, en transformant simplement le membre de remise en une propriété avec la propriété get y set blocs :

function Product(name,price) {
  this.name = name;
  this.price = price;
//this.discount = 0; // <- remove this line and refactor with the code below
  var _discount; // private member
  Object.defineProperty(this,"discount",{
    get: function() { return _discount; },
    set: function(value) { _discount = value; if(_discount>80) _discount = 80; }
  });
}

// the client code
var sneakers = new Product("Sneakers",20);
sneakers.discount = 50; // 50, setter is called
sneakers.discount+= 20; // 70, setter is called
sneakers.discount+= 20; // 80, not 90!
alert(sneakers.discount); // getter is called

Notez l'avant-dernière ligne : la responsabilité de la valeur correcte de la remise a été déplacée du code client (définition de l'e-shop) à la définition du produit. Le produit est responsable de la cohérence des membres de ses données. Une bonne conception est (dit grossièrement) si le code fonctionne de la même manière que nos pensées.

Tant de choses sur les propriétés. Mais javascript est différent des langages purement orientés objet comme C# et code les caractéristiques différemment :

En C# la transformation des champs en propriétés est une changement brutal Les champs publics doivent donc être codés comme Propriétés auto-implémentées si votre code peut être utilisé dans le client compilé séparément.

En Javascript les propriétés standard (membre de données avec getter et setter décrits ci-dessus) sont définies par descripteur d'accès (dans le lien que vous avez dans votre question). En exclusivité, vous pouvez utiliser descripteur de données (vous ne pouvez donc pas utiliser i.e. value y set sur la même propriété) :

  • descripteur d'accès \= obtenir + fixer (voir l'exemple ci-dessus)
    • obtenir doit être une fonction ; sa valeur de retour est utilisée dans la lecture de la propriété ; si elle n'est pas spécifiée, la valeur par défaut est indéfini qui se comporte comme une fonction qui renvoie une valeur indéfinie.
    • set doit être une fonction ; son paramètre est rempli avec RHS dans l'attribution d'une valeur à la propriété ; si elle n'est pas spécifiée, la valeur par défaut est indéfini qui se comporte comme une fonction vide
  • descripteur de données \= valeur + inscriptible (voir l'exemple ci-dessous)
    • value par défaut indéfini ; si accessible en écriture , configurable y énumérable (voir ci-dessous) sont vrais, la propriété se comporte comme un champ de données ordinaire.
    • accessible en écriture - défaut faux ; sinon vrai la propriété est en lecture seule ; toute tentative d'écriture est ignorée sans erreur* !

Les deux descripteurs peuvent avoir ces membres :

  • configurable - défaut faux ; si ce n'est pas vrai, la propriété ne peut pas être supprimée ; la tentative de suppression est ignorée sans erreur* !
  • énumérable - défaut faux ; si c'est vrai, il sera itéré en for(var i in theObject) Si elle est fausse, elle ne sera pas itérée, mais elle est toujours accessible en tant que publique.

* sauf si dans mode strict - dans ce cas, JS arrête l'exécution avec TypeError à moins qu'il ne soit attrapé dans bloc try-catch

Pour lire ces paramètres, utilisez Object.getOwnPropertyDescriptor() .

Apprenez par l'exemple :

var o = {};
Object.defineProperty(o,"test",{
  value: "a",
  configurable: true
});
console.log(Object.getOwnPropertyDescriptor(o,"test")); // check the settings    

for(var i in o) console.log(o[i]); // nothing, o.test is not enumerable
console.log(o.test); // "a"
o.test = "b"; // o.test is still "a", (is not writable, no error)
delete(o.test); // bye bye, o.test (was configurable)
o.test = "b"; // o.test is "b"
for(var i in o) console.log(o[i]); // "b", default fields are enumerable

Si vous ne souhaitez pas autoriser le code client à tricher de la sorte, vous pouvez restreindre l'objet par trois niveaux de confinement :

  • Object.preventExtensions(votreObjet) empêche l'ajout de nouvelles propriétés à votreObjet . Utilisez Object.isExtensible(<yourObject>) pour vérifier si la méthode a été utilisée sur l'objet. La prévention est peu profond (lire ci-dessous).
  • Object.seal(votreObjet) même chose que ci-dessus et les propriétés ne peuvent pas être supprimées (ce qui a pour effet de définir configurable: false à toutes les propriétés). Utilisez Object.isSealed(<yourObject>) pour détecter cette caractéristique sur l'objet. Le sceau est peu profond (lire ci-dessous).
  • Object.freeze(votreObjet) comme ci-dessus et les propriétés ne peuvent pas être modifiées (en fait, il s'agit d'un ensemble d'éléments) writable: false à toutes les propriétés avec descripteur de données). La propriété inscriptible de Setter n'est pas affectée (puisqu'elle n'en a pas). Le gel est peu profond : cela signifie que si la propriété est Objet, ses propriétés NE SONT PAS gelées (si vous le souhaitez, vous devez effectuer quelque chose comme "geler profondément", similaire à copie profonde - clonage ). Utilisez Object.isFrozen(<yourObject>) pour le détecter.

Il n'est pas nécessaire de s'en préoccuper si vous n'écrivez que quelques lignes amusantes. Mais si vous voulez coder un jeu (comme vous l'avez mentionné dans la question liée), vous devez vous soucier d'une bonne conception. Essayez de chercher sur Google quelque chose sur anti-modèles y odeur du code . Cela vous permettra d'éviter des situations telles que "Oh, je dois réécrire complètement mon code à nouveau !" il peut vous éviter des mois de désespoir si vous voulez coder beaucoup. Bonne chance.

0 votes

Cette partie est claire. function Product(name,price) { this.name = name; this.price = price; var _discount; // private member Object.defineProperty(this,"discount",{ get: function() { return _discount; }, set: function(value) { _discount = value; if(_discount>80) _discount = 80; } }); } var sneakers = new Product("Sneakers",20); sneakers.discount = 50; // 50, setter is called sneakers.discount+= 20; // 70, setter is called sneakers.discount+= 20; // 80, not 90! alert(sneakers.discount); // getter is called

27voto

Paulpro Points 54844

get est une fonction qui est appelée lorsque vous essayez de lire la valeur player.health comme dans :

console.log(player.health);

Ce n'est effectivement pas très différent de :

player.getHealth = function(){
  return 10 + this.level*15;
}
console.log(player.getHealth());

L'opposé de get est set, qui serait utilisé lorsque vous assignez à la valeur. Comme il n'y a pas de setter, il semble que l'assignation à la santé du joueur ne soit pas prévue :

player.health = 5; // Doesn't do anything, since there is no set function defined

Un exemple très simple :

var player = {
  level: 5
};

Object.defineProperty(player, "health", {
  get: function() {
    return 10 + (player.level * 15);
  }
});

console.log(player.health); // 85
player.level++;
console.log(player.health); // 100

player.health = 5; // Does nothing
console.log(player.health); // 100

0 votes

C'est comme une fonction que vous n'avez pas besoin d'utiliser. () pour appeler... Je ne comprends pas quelle était l'idée quand ils ont inventé ce truc. Les fonctions sont totalement identiques : jsbin.com/bugipi/edit?js,console,output

19voto

JEMI Points 429

defineProperty est une méthode sur les objets qui vous permet de configurer les propriétés pour répondre à certains critères. Voici un exemple simple avec un objet employé avec deux propriétés firstName & lastName et ajouter les deux propriétés en surchargeant la méthode toString sur l'objet.

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
employee.toString=function () {
    return this.firstName + " " + this.lastName;
};
console.log(employee.toString());

Vous obtiendrez la sortie comme : Jameel Moideen

Je vais modifier le même code en utilisant defineProperty sur l'objet

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: true,
    enumerable: true,
    configurable: true
});
console.log(employee.toString());

Le premier paramètre est le nom de l'objet et ensuite le deuxième paramètre est le nom de la propriété que nous ajoutons, dans notre cas c'est toString et ensuite le dernier paramètre est l'objet json qui a une valeur qui va être une fonction et trois paramètres writable,enumerable et configurable.Pour le moment je viens de déclarer tout comme true.

Si vous exécutez l'exemple, vous obtiendrez la sortie suivante : Jameel Moideen

Comprenons pourquoi nous avons besoin des trois propriétés suivantes accessible en écriture, énumérable et configurable.

accessible en écriture

Une des parties les plus ennuyeuses du javascript est que, si vous changez la propriété toString en quelque chose d'autre, par exemple

enter image description here

si tu le refais, tout sera cassé. Changeons writable en false. Si vous exécutez la même chose à nouveau, vous obtiendrez la sortie correcte comme 'Jameel Moideen'. Cette propriété empêchera d'écraser cette propriété plus tard.

énumérable

Si vous imprimez toutes les clés à l'intérieur de l'objet, vous pouvez voir toutes les propriétés, y compris toString.

console.log(Object.keys(employee));

enter image description here

si vous mettez enumerable à false , vous pouvez cacher la propriété toString de tout le monde. Si vous exécutez à nouveau cette opération, vous obtiendrez firstName,lastName

configurable

si quelqu'un a redéfini plus tard l'objet sur par exemple enumerable à true et l'exécuter. Vous pouvez voir que la propriété toString est revenue.

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: false,
    enumerable: false,
    configurable: true
});

//change enumerable to false
Object.defineProperty(employee, 'toString', {

    enumerable: true
});
employee.toString="changed";
console.log(Object.keys(employee));

enter image description here

vous pouvez restreindre ce comportement en définissant configurable à false.

La référence originale de cette information provient de mon blog personnel.

2voto

Cole Pilegard Points 357

En gros, defineProperty est une méthode qui prend en compte 3 paramètres - un objet, une propriété et un descripteur. Ce qui se passe dans cet appel particulier est que le "health" de l player est assigné à 10 plus 15 fois le niveau de cet objet joueur.

0voto

ISONecroMAn Points 542

Object.defineProperty() est une fonction globale... Elle n'est pas disponible à l'intérieur de la fonction qui déclare l'objet sinon, vous devrez l'utiliser statiquement...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X