Verwendung von Javascript Object.defineProperty

183

Ich sah mich nach der Object.definePropertyMethode um, konnte aber nichts Anständiges finden.

Jemand hat mir diesen Codeausschnitt gegeben :

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

Aber ich verstehe es nicht. Hauptsächlich ist das getdas, was ich nicht bekommen kann (Wortspiel beabsichtigt). Wie funktioniert es?

Mathekühler
quelle
1
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Dies ist hier ein ausgezeichnetes Tutorial.
Martian2049

Antworten:

498

Da Sie eine ähnliche Frage gestellt haben , gehen wir Schritt für Schritt vor. Es ist etwas länger, aber es kann Ihnen viel mehr Zeit sparen, als ich für das Schreiben aufgewendet habe:

Property ist eine OOP-Funktion zur sauberen Trennung von Client-Code. In einigen E-Shops gibt es beispielsweise folgende Objekte:

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}

In Ihrem Kundencode (dem E-Shop) können Sie Ihren Produkten Rabatte hinzufügen:

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

Später kann der E-Shop-Besitzer feststellen, dass der Rabatt nicht größer als 80% sein kann. Jetzt müssen Sie JEDES Auftreten der Rabattänderung im Kundencode finden und eine Zeile hinzufügen

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

Dann kann der E-Shop-Besitzer seine Strategie weiter ändern, z. B. "Wenn der Kunde Wiederverkäufer ist, kann der maximale Rabatt 90% betragen" . Und Sie müssen die Änderung an mehreren Stellen erneut vornehmen und daran denken, diese Zeilen jedes Mal zu ändern, wenn die Strategie geändert wird. Das ist ein schlechtes Design. Deshalb ist die Kapselung das Grundprinzip von OOP. Wenn der Konstruktor so wäre:

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; } 
}

Dann können Sie einfach die Methoden getDiscount( Accessor ) und setDiscount( Mutator ) ändern . Das Problem ist, dass sich die meisten Mitglieder wie gemeinsame Variablen verhalten, nur der Rabatt bedarf hier besonderer Sorgfalt. Für ein gutes Design muss jedoch jedes Datenelement gekapselt werden, damit der Code erweiterbar bleibt. Sie müssen also viel Code hinzufügen, der nichts bewirkt. Dies ist auch ein schlechtes Design, ein Boilerplate Antipattern . Manchmal können Sie die Felder nicht einfach später in Methoden umgestalten (der Eshop-Code wird möglicherweise größer oder ein Code von Drittanbietern hängt von der alten Version ab), sodass das Boilerplate hier weniger böse ist. Trotzdem ist es böse. Aus diesem Grund wurden Eigenschaften in viele Sprachen eingeführt. Sie können den ursprünglichen Code beibehalten, indem Sie einfach das Rabattmitglied in eine Eigenschaft mit umwandelngetund setBlöcke:

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

Beachten Sie die vorletzte Zeile: Die Verantwortung für den korrekten Rabattwert wurde vom Kundencode (E-Shop-Definition) in die Produktdefinition verschoben. Das Produkt ist dafür verantwortlich, dass seine Datenmitglieder konsistent bleiben. Gutes Design ist (grob gesagt), wenn der Code genauso funktioniert wie unsere Gedanken.

Soviel zu Eigenschaften. Javascript unterscheidet sich jedoch von reinen objektorientierten Sprachen wie C # und codiert die Funktionen unterschiedlich:

In C # ist die Umwandlung von Feldern in Eigenschaften eine wichtige Änderung . Daher sollten öffentliche Felder als automatisch implementierte Eigenschaften codiert werden, wenn Ihr Code möglicherweise in einem separat kompilierten Client verwendet wird.

In Javascript werden die Standardeigenschaften (Datenelement mit Getter und Setter, wie oben beschrieben) durch den Accessor-Deskriptor (in dem Link, den Sie in Ihrer Frage haben) definiert. Exklusiv können Sie verwenden Daten Descriptor (so dass Sie nicht verwenden können , dh Wert und Satz auf dem gleichen Grundstück):

  • Accessor Descriptor = get + set (siehe Beispiel oben)
    • get muss eine Funktion sein; Der Rückgabewert wird beim Lesen der Eigenschaft verwendet. Wenn nicht angegeben, ist der Standardwert undefiniert . Dies verhält sich wie eine Funktion, die undefiniert zurückgibt
    • set muss eine Funktion sein; Sein Parameter wird mit RHS gefüllt, wenn der Eigenschaft ein Wert zugewiesen wird. Wenn nicht angegeben, ist der Standardwert undefiniert , was sich wie eine leere Funktion verhält
  • Datendeskriptor = Wert + beschreibbar (siehe Beispiel unten)
    • Wert Standard undefiniert ; Wenn beschreibbar , konfigurierbar und aufzählbar (siehe unten) wahr sind, verhält sich die Eigenschaft wie ein gewöhnliches Datenfeld
    • beschreibbar - Standardwert falsch ; Wenn dies nicht der Fall ist , ist die Eigenschaft schreibgeschützt. Schreibversuch wird fehlerfrei ignoriert *!

Beide Deskriptoren können folgende Mitglieder haben:

  • konfigurierbar - Standardwert false ; Wenn dies nicht der Fall ist, kann die Eigenschaft nicht gelöscht werden. Löschversuch wird fehlerfrei ignoriert *!
  • enumerable - default false ; Wenn dies der Fall ist, wird es wiederholtfor(var i in theObject). Wenn false, wird es nicht iteriert, ist aber weiterhin als öffentlich zugänglich

* außer im strengen Modus - in diesem Fall stoppt JS die Ausführung mit TypeError, es sei denn, es wird im Try-Catch-Block abgefangen

Verwenden Sie zum Lesen dieser Einstellungen Object.getOwnPropertyDescriptor().

Lernen Sie anhand eines Beispiels:

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

Wenn Sie dem Client-Code solche Cheats nicht erlauben möchten, können Sie das Objekt um drei Begrenzungsstufen einschränken:

  • Object.preventExtensions (yourObject) verhindert, dass IhremObject neue Eigenschaften hinzugefügt werden . Verwenden SieObject.isExtensible(<yourObject>)dieseOption, um zu überprüfen, ob die Methode für das Objekt verwendet wurde. Die Prävention ist flach (siehe unten).
  • Object.seal (yourObject) wie oben und Eigenschaften können nicht entfernt werden (wird effektivconfigurable: falseauf alle Eigenschaften festgelegt). Verwenden SieObject.isSealed(<yourObject>)dieseOption, um diese Funktion am Objekt zu erkennen. Das Siegel ist flach (siehe unten).
  • Object.freeze (yourObject) wie oben und Eigenschaften können nicht geändert werden (effektiv werdenwritable: falsealle Eigenschaften mit Datendeskriptor festgelegt ). Die beschreibbare Eigenschaft von Setter ist nicht betroffen (da sie keine hat). Das Einfrieren ist flach : Wenn die Eigenschaft Objekt ist, werden ihre Eigenschaften NICHT eingefroren (wenn Sie möchten, sollten Sie so etwas wie "Deep Freeze" ausführen, ähnlich wie beim Deep Copy-Klonen ). Verwenden SieObject.isFrozen(<yourObject>), um es zu erkennen.

Sie müssen sich nicht darum kümmern, wenn Sie nur ein paar Zeilen schreiben, die Spaß machen. Aber wenn Sie ein Spiel codieren möchten (wie Sie in der verknüpften Frage erwähnt haben), sollten Sie sich wirklich um gutes Design kümmern. Versuchen Sie, etwas über google Antipatterns und Codegeruch . Es wird Ihnen helfen, Situationen wie "Oh, ich muss meinen Code komplett neu schreiben!" Zu vermeiden. Es kann Ihnen Monate der Verzweiflung ersparen, wenn Sie viel codieren möchten. Viel Glück.

Jan Turoň
quelle
Dieser Teil ist klar. 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
Abu Abu
27

getist eine Funktion, die aufgerufen wird, wenn Sie versuchen, den Wert zu lesen player.health, wie in:

console.log(player.health);

Es ist effektiv nicht viel anders als:

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

Das Gegenteil von get wird gesetzt, das verwendet wird, wenn Sie dem Wert zuweisen. Da es keinen Setter gibt, scheint es nicht beabsichtigt zu sein, der Gesundheit des Spielers zuzuweisen:

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

Ein sehr einfaches Beispiel:

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

Paul
quelle
Es ist wie eine Funktion, die Sie nicht ()zum Aufrufen verwenden müssen ... Ich verstehe nicht, was die Idee war, als sie dieses Ding erfunden haben. Die Funktionen sind völlig gleich: jsbin.com/bugipi/edit?js,console,output
vsync
15

defineProperty ist eine Methode für Object, mit der Sie die Eigenschaften so konfigurieren können, dass sie einige Kriterien erfüllen. Hier ist ein einfaches Beispiel mit einem Mitarbeiterobjekt mit zwei Eigenschaften firstName & lastName. Fügen Sie die beiden Eigenschaften hinzu, indem Sie die toString- Methode für das Objekt überschreiben .

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

Sie erhalten die Ausgabe als: Jameel Moideen

Ich werde den gleichen Code ändern, indem ich defineProperty für das Objekt verwende

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());

Der erste Parameter ist der Name des Objekts und der zweite Parameter ist der Name der Eigenschaft, die wir hinzufügen. In unserem Fall ist es toString. Der letzte Parameter ist ein json-Objekt, dessen Wert eine Funktion sein wird, und drei Parameter, die beschreibbar und aufzählbar sind und jetzt konfigurierbar. Ich habe gerade alles als wahr deklariert.

Wenn Sie das Beispiel ausführen, erhalten Sie die Ausgabe als: Jameel Moideen

Lassen Sie uns verstehen, warum wir die drei Eigenschaften wie beschreibbar, aufzählbar und konfigurierbar benötigen .

schreibbar

Einer der sehr ärgerlichen Teile des Javascript ist, wenn Sie beispielsweise die Eigenschaft toString in etwas anderes ändern

Geben Sie hier die Bildbeschreibung ein

Wenn Sie dies erneut ausführen, wird alles unterbrochen. Lassen Sie uns beschreibbar in falsch ändern. Wenn Sie dasselbe erneut ausführen, erhalten Sie die korrekte Ausgabe als 'Jameel Moideen'. Diese Eigenschaft verhindert, dass diese Eigenschaft später überschrieben wird.

aufzählbar

Wenn Sie alle Schlüssel im Objekt drucken, werden alle Eigenschaften einschließlich toString angezeigt.

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

Geben Sie hier die Bildbeschreibung ein

Wenn Sie enumerable auf false setzen, können Sie die Eigenschaft toString vor allen anderen ausblenden. Wenn Sie dies erneut ausführen, erhalten Sie Vorname, Nachname

konfigurierbar

Wenn jemand das Objekt später neu definiert, z. B. auf true aufzählbar, und es ausführen kann. Sie können sehen, dass die toString-Eigenschaft erneut verfügbar ist.

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));

Geben Sie hier die Bildbeschreibung ein

Sie können dieses Verhalten einschränken, indem Sie configure auf false setzen.

Der ursprüngliche Verweis auf diese Informationen stammt aus meinem persönlichen Blog

Code-EZ
quelle
1
Ich verstehe, dass Sie dies in Ihrem Blog hatten und es gerade hier eingefügt haben, aber zumindest für die Zukunft wissen: Screencaps sind bei SO nicht beliebt. Sie können den Code nicht kopieren, um ihn zu testen, und der Code wird von Suchmaschinen oder unterstützenden Technologien nicht angezeigt.
Domino
@JacqueGoupil Sie haben Recht. Ich werde aktualisieren, indem ich Code anstelle von Screenshot hinzufüge
Code-EZ
3

Grundsätzlich definePropertyhandelt es sich um eine Methode, die drei Parameter berücksichtigt - ein Objekt, eine Eigenschaft und einen Deskriptor. Was in diesem speziellen Aufruf passiert, ist, dass die "health"Eigenschaft des playerObjekts dem 10-fachen des 15-fachen Levels des Spielerobjekts zugewiesen wird.

Cole Pilegard
quelle
0

ja nein weitere funktionserweiterung für setup setter & getter dies ist mein beispiel Object.defineProperty (obj, name, func)

var obj = {};
['data', 'name'].forEach(function(name) {
    Object.defineProperty(obj, name, {
        get : function() {
            return 'setter & getter';
        }
    });
});


console.log(obj.data);
console.log(obj.name);
Faizal Pribadi
quelle
0

Object.defineProperty () ist eine globale Funktion. Sie ist nicht in der Funktion verfügbar, die das Objekt anderweitig deklariert. Sie müssen es statisch verwenden ...

ISONecroMAn
quelle
0

Zusammenfassung:

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

Object.definePropertywird verwendet, um eine neue Eigenschaft für das Player-Objekt zu erstellen. Object.definePropertyist eine Funktion, die nativ in der JS-Laufzeitumgebung vorhanden ist und die folgenden Argumente akzeptiert:

Object.defineProperty(obj, prop, descriptor)

  1. Das Objekt, für das wir eine neue Eigenschaft definieren möchten
  2. Der Name der neuen Eigenschaft, die wir definieren möchten
  3. Deskriptorobjekt

Das Deskriptorobjekt ist der interessante Teil. Hier können wir folgende Dinge definieren:

  1. konfigurierbar <boolean> : Wenn true der Eigenschaftsdeskriptor geändert und die Eigenschaft aus dem Objekt gelöscht werden kann. Wenn konfigurierbar, können falsedie übergebenen Deskriptoreigenschaften Object.definePropertynicht geändert werden.
  2. Beschreibbar <boolean> : Wenn truedie Eigenschaft mit dem Zuweisungsoperator überschrieben werden kann.
  3. Aufzählbar <boolean> : Wenn true die Eigenschaft in einer for...inSchleife wiederholt werden kann. Auch bei Verwendung der Object.keysFunktion ist die Taste vorhanden. Wenn die Eigenschaft ist, werden falsesie nicht mit einer for..inSchleife durchlaufen und bei der Verwendung nicht angezeigt Object.keys.
  4. get <function> : Eine Funktion, die immer dann aufgerufen wird, wenn die Eigenschaft erforderlich ist. Anstatt den direkten Wert anzugeben, wird diese Funktion aufgerufen und der zurückgegebene Wert als Wert der Eigenschaft angegeben
  5. set <function> : Eine Funktion, die immer dann aufgerufen wird, wenn die Eigenschaft zugewiesen ist. Anstatt den direkten Wert festzulegen, wird diese Funktion aufgerufen und der zurückgegebene Wert wird verwendet, um den Wert der Eigenschaft festzulegen.

Beispiel:

const player = {
  level: 10
};

Object.defineProperty(player, "health", {
  configurable: true,
  enumerable: false,
  get: function() {
    console.log('Inside the get function');
    return 10 + (player.level * 15);
  }
});

console.log(player.health);
// the get function is called and the return value is returned as a value

for (let prop in player) {
  console.log(prop);
  // only prop is logged here, health is not logged because is not an iterable property.
  // This is because we set the enumerable to false when defining the property
}

Willem van der Veen
quelle
0

import { CSSProperties } from 'react'
import { BLACK, BLUE, GREY_DARK, WHITE } from '../colours'

export const COLOR_ACCENT = BLUE
export const COLOR_DEFAULT = BLACK
export const FAMILY = "'Segoe UI', sans-serif"
export const SIZE_LARGE = '26px'
export const SIZE_MEDIUM = '20px'
export const WEIGHT = 400

type Font = {
  color: string,
  size: string,
  accent: Font,
  default: Font,
  light: Font,
  neutral: Font,
  xsmall: Font,
  small: Font,
  medium: Font,
  large: Font,
  xlarge: Font,
  xxlarge: Font
} & (() => CSSProperties)

function font (this: Font): CSSProperties {
  const css = {
    color: this.color,
    fontFamily: FAMILY,
    fontSize: this.size,
    fontWeight: WEIGHT
  }
  delete this.color
  delete this.size
  return css
}

const dp = (type: 'color' | 'size', name: string, value: string) => {
  Object.defineProperty(font, name, { get () {
    this[type] = value
    return this
  }})
}

dp('color', 'accent', COLOR_ACCENT)
dp('color', 'default', COLOR_DEFAULT)
dp('color', 'light', COLOR_LIGHT)
dp('color', 'neutral', COLOR_NEUTRAL)
dp('size', 'xsmall', SIZE_XSMALL)
dp('size', 'small', SIZE_SMALL)
dp('size', 'medium', SIZE_MEDIUM)

export default font as Font

Alvin Smith
quelle
0

Definiert eine neue Eigenschaft direkt für ein Objekt oder ändert eine vorhandene Eigenschaft für ein Objekt und gibt das Objekt zurück.

Hinweis: Sie rufen diese Methode direkt im Objektkonstruktor und nicht in einer Instanz vom Typ Object auf.

   const object1 = {};
   Object.defineProperty(object1, 'property1', {
      value: 42,
      writable: false, //If its false can't modify value using equal symbol
      enumerable: false, // If its false can't able to get value in Object.keys and for in loop
      configurable: false //if its false, can't able to modify value using defineproperty while writable in false
   });

Geben Sie hier die Bildbeschreibung ein

Einfache Erklärung zum Definieren von Eigenschaften.

Beispielcode: https://jsfiddle.net/manoj_antony32/pu5n61fs/

Mano
quelle
0

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

console.log([1,2,3,4].last) //returns 4

Jaeyson Anthony Y.
quelle