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 umwandelnget
und set
Blö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 wiederholt
for(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 Sie
Object.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 effektiv
configurable: false
auf 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 werden
writable: false
alle 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.