Dato che hai posto una domanda simile , facciamo un passo alla volta. È un po 'più lungo, ma potrebbe farti risparmiare molto più tempo di quanto abbia speso per scrivere questo:
La proprietà è una funzionalità OOP progettata per una netta separazione del codice client. Ad esempio, in alcuni e-shop potresti avere oggetti come questo:
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}
Quindi nel tuo codice cliente (l'e-shop), puoi aggiungere sconti ai tuoi prodotti:
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
In seguito, il proprietario dell'e-shop potrebbe rendersi conto che lo sconto non può essere superiore all'80%. Ora è necessario trovare OGNI occorrenza della modifica dello sconto nel codice client e aggiungere una riga
if(obj.discount>80) obj.discount = 80;
Quindi il proprietario dell'e-shop può cambiare ulteriormente la sua strategia, ad esempio "se il cliente è rivenditore, lo sconto massimo può essere del 90%" . Ed è necessario apportare nuovamente la modifica su più posizioni, inoltre è necessario ricordare di modificare queste righe ogni volta che la strategia viene modificata. Questo è un cattivo design. Ecco perché l' incapsulamento è il principio base di OOP. Se il costruttore fosse così:
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; }
}
Quindi puoi semplicemente modificare i metodi getDiscount
( accessor ) e setDiscount
( mutator ). Il problema è che la maggior parte dei membri si comporta come variabili comuni, solo lo sconto ha bisogno di cure speciali qui. Ma una buona progettazione richiede l'incapsulamento di ogni membro di dati per mantenere il codice estensibile. Quindi è necessario aggiungere un sacco di codice che non fa nulla. Anche questo è un pessimo design, un antipasto a base di caldaia . A volte non è possibile semplicemente convertire i campi in metodi in un secondo momento (il codice eshop potrebbe ingrandirsi o alcuni codici di terze parti potrebbero dipendere dalla versione precedente), quindi la piastra della caldaia è meno male qui. Tuttavia, è malvagio. Ecco perché le proprietà sono state introdotte in molte lingue. È possibile mantenere il codice originale, semplicemente trasformare il membro dello sconto in una proprietà conget
e set
blocchi:
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
Nota l'ultima ma una riga: la responsabilità per il valore di sconto corretto è stata spostata dal codice cliente (definizione e-shop) alla definizione del prodotto. Il prodotto è responsabile di mantenere coerenti i propri membri dei dati. Un buon design è (approssimativamente detto) se il codice funziona allo stesso modo dei nostri pensieri.
Tanto sulle proprietà. Ma JavaScript è diverso dai linguaggi puri orientati agli oggetti come C # e codifica le funzionalità in modo diverso:
In C # , la trasformazione dei campi in proprietà è una modifica sostanziale , pertanto i campi pubblici devono essere codificati come Proprietà implementate automaticamente se il codice potrebbe essere utilizzato in client compilati separatamente.
In Javascript , le proprietà standard (membro di dati con getter e setter descritti sopra) sono definite dal descrittore di accesso (nel collegamento presente nella domanda). Esclusivamente, è possibile utilizzare il descrittore di dati (quindi non è possibile utilizzare vale a dire valore e impostare sulla stessa proprietà):
- descrittore accessor = get + set (vedere l'esempio sopra)
- get deve essere una funzione; il suo valore di ritorno viene utilizzato nella lettura della proprietà; se non specificato, il valore predefinito non è definito , il che si comporta come una funzione che restituisce un valore non definito
- set deve essere una funzione; il suo parametro è riempito con RHS nell'assegnare un valore alla proprietà; se non specificato, il valore predefinito non è definito , il che si comporta come una funzione vuota
- descrittore dati = valore + scrivibile (vedere l'esempio seguente)
- valore predefinito non definito ; se scrivibile , configurabile ed enumerabile (vedi sotto) sono vere, la proprietà si comporta come un normale campo dati
- scrivibile - default false ; se non è vero , la proprietà è di sola lettura; il tentativo di scrivere viene ignorato senza errori *!
Entrambi i descrittori possono avere questi membri:
- configurabile - default false ; se non è vero, la proprietà non può essere eliminata; il tentativo di eliminazione viene ignorato senza errori *!
- enumerabile : impostazione predefinita false ; se vero, verrà ripetuto
for(var i in theObject)
; se falso, non verrà ripetuto, ma sarà comunque accessibile come pubblico
* a meno che non sia in modalità rigorosa : in tal caso JS interrompe l'esecuzione con TypeError a meno che non venga rilevato nel blocco try-catch
Per leggere queste impostazioni, utilizzare Object.getOwnPropertyDescriptor()
.
Impara con l'esempio:
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
Se non si desidera consentire al codice client tali trucchi, è possibile limitare l'oggetto di tre livelli di confinamento:
- Object.preventExtensions (yourObject) impedisce l'aggiunta di nuove proprietà a yourObject . Utilizzare
Object.isExtensible(<yourObject>)
per verificare se il metodo è stato utilizzato sull'oggetto. La prevenzione è superficiale (leggi sotto).
- Object.seal (yourObject) come sopra e le proprietà non possono essere rimosse (impostate efficacemente
configurable: false
su tutte le proprietà). UtilizzareObject.isSealed(<yourObject>)
per rilevare questa funzione sull'oggetto. Il sigillo è poco profondo (leggi sotto).
- Object.freeze (yourObject) come sopra e le proprietà non possono essere modificate (imposta effettivamente
writable: false
tutte le proprietà con descrittore di dati). La proprietà scrivibile di Setter non è interessata (poiché non ne ha una). Il congelamento è superficiale : significa che se la proprietà è Object, le sue proprietà NON SONO bloccate (se lo si desidera, è necessario eseguire qualcosa di simile a "congelamento profondo", simile alla copia profonda - clonazione ). UtilizzareObject.isFrozen(<yourObject>)
per rilevarlo.
Non devi preoccuparti di questo se scrivi solo poche righe divertenti. Ma se vuoi codificare un gioco (come hai detto nella domanda collegata), dovresti davvero preoccuparti di un buon design. Prova a google qualcosa su antipattern e odore di codice . Ti aiuterà a evitare situazioni come "Oh, devo riscrivere completamente il mio codice!" , può farti risparmiare mesi di disperazione se vuoi programmare molto. In bocca al lupo.