Dal momento che hai chiesto un similar question, facciamo un passo alla volta. È un po 'più lungo, ma potrebbe risparmiare molto più tempo di quello che ho speso scrivendo:
La proprietà è una funzionalità OOP progettata per la separazione netta del codice cliente. Per esempio, in alcune e-shop si potrebbe 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}
Poi nel codice del client (e-shop), è possibile aggiungere sconti ai vostri prodotti:
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
tardi , il proprietario dell'e-shop potrebbe rendersi conto che lo sconto non può essere maggiore di quello dell'80%. Ora è necessario trovare ogni occorrenza della modifica di sconto nel codice client e aggiungere una linea
if(obj.discount>80) obj.discount = 80;
Poi il proprietario e-shop può cambiare ulteriormente la sua strategia, come "se il cliente è rivenditore, lo sconto massimo può essere del 90% ". E devi fare di nuovo la modifica su più posti oltre a dover ricordare di modificare queste righe ogni volta che la strategia viene cambiata. Questo è un cattivo design. Ecco perché incapsulamento è il principio di base di OOP.Se il costruttore era come questo:
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; }
}
allora si può solo altera il getDiscount
(di accesso) e setDiscount
(mutator) metodi. Il problema è che la maggior parte dei membri si comporta come variabili comuni, solo lo sconto ha bisogno di cure particolari qui. Ma un buon design richiede l'incapsulamento di ogni membro dei dati per mantenere il codice estensibile. Quindi è necessario aggiungere un sacco di codice che non fa nulla. Anche questo è un cattivo design, un antipasto. A volte non puoi semplicemente ridefinire i campi con i metodi in un secondo momento (il codice eshop potrebbe crescere molto o alcuni codici di terze parti potrebbero dipendere dalla vecchia versione), quindi il boilerplate è il male minore qui. Ma ancora, è il male. Ecco perché le proprietà sono state introdotte in molte lingue. Si potrebbe mantenere il codice originale, basta trasformare il membro di sconto in una proprietà con get
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 la penultima riga: la responsabilità per il valore di sconto corretta è stata spostata dal codice client (e- definizione del negozio) alla definizione del prodotto. Il prodotto è responsabile di mantenere coerenti i propri membri dei dati. Il buon design è (approssimativamente detto) se il codice funziona allo stesso modo dei nostri pensieri.
Tanto sulle proprietà. Ma javascript è diverso dal puro linguaggi orientati agli oggetti come C# e codici le caratteristiche in modo diverso:
In C#, trasformando i campi nelle proprietà è un breaking change, quindi i campi pubblici devono essere codificati come Auto-Implemented Properties se il codice potrebbe essere utilizzato in cliente compilato in modo separato.
in JavaScript, le proprietà standard (membro di dati con getter e setter descritto sopra) sono definiti da di accesso descrittore (nel link che hai nella tua domanda). Esclusivamente, è possibile utilizzare il descrittore di dati (quindi non è possibile utilizzare i.e.valore e impostati sul terreno):
- descrittore di accesso = get + set (vedere l'esempio precedente)
- ottenere deve essere una funzione; il suo valore di ritorno è usato nella lettura della proprietà; se non specificato, il default è indefinito, che si comporta come una funzione che restituisce indefinito
- impostato deve essere una funzione; il suo parametro è riempito con RHS nell'assegnazione di un valore alla proprietà; se non specificato, il default è indefinito, che si comporta come una funzione vuota
- descrittore di dati = valore + scrivibile (vedere l'esempio seguente)
- valore predefinito indefinito; se scrivibile, configurabile e enumerabile (vedi sotto) sono vere, la proprietà si comporta come un normale campo di dati
- scrivibile - Default falsa; in caso contrario, true, la proprietà è di sola lettura; il tentativo di scrivere viene ignorato senza errore *!
Entrambi i descrittori possono avere questi membri:
- configurabili - Default falsa; se non è vero, la proprietà non può essere cancellata; il tentativo di eliminazione viene ignorato senza errore *!
- enumerabile - default falsa; se true, verrà iterato in
for(var i in theObject)
; se è falso, non verrà ripetuta, ma è ancora accessibile come pubblico
* a meno che in strict mode - in quel caso JS interrompe l'esecuzione con TypeError meno che non sia preso in try-catch block
Per leggere questi impostazioni, utilizzare Object.getOwnPropertyDescriptor()
.
Impara per esempi:
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 il codice del client tali trucchi, è possibile limitare l'oggetto da tre livelli di confinamento:
- Object.preventExtensions(yourObject) impedisce nuove proprietà da aggiungere a yourObject. Utilizzare
Object.isExtensible(<yourObject>)
per verificare se il metodo è stato utilizzato sull'oggetto. La prevenzione è shallow (leggi sotto).
- Object.seal(yourObject) come sopra e le proprietà non possono essere rimosse (imposta in modo efficace
configurable: false
su tutte le proprietà). Utilizzare Object.isSealed(<yourObject>)
per rilevare questa funzione sull'oggetto. Il sigillo è superficiale (leggi sotto).
- Object.freeze(yourObject) come sopra e le proprietà non possono essere modificate (imposta in modo efficace
writable: false
in tutte le proprietà con descrittore di dati). La proprietà scrivibile di Setter non è influenzata (poiché non ne ha uno). Il blocco è shallow: significa che se la proprietà è Object, le sue proprietà NON SONO congelate (se lo si desidera, è necessario eseguire qualcosa come "deep freeze", simile a deep copy - cloning). Utilizzare Object.isFrozen(<yourObject>)
per rilevarlo.
Non è necessario preoccuparsi di questo se si scrivono solo poche righe divertenti. Ma se vuoi codificare un gioco (come hai detto nella domanda collegata), dovresti davvero preoccuparti del buon design. Prova a google qualcosa su antipatterns e codice odore. Ti aiuterà a evitare situazioni come "Oh, ho bisogno di riscrivere completamente il mio codice ancora!", può farti risparmiare mesi di disperazione se vuoi programmare molto. In bocca al lupo.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty questo è un ottimo tutorial qui. – Martian2049