2013-08-30 13 views
128

Mi sono guardato intorno per sapere come usare il metodo Object.defineProperty, ma non ho trovato nulla di decente.come usare javascript Object.defineProperty

Qualcuno mi ha dato this snippet of code:

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

Ma io non lo capisco. Principalmente, lo get è ciò che non riesco a ottenere (gioco di parole). Come funziona?

+0

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty questo è un ottimo tutorial qui. – Martian2049

risposta

386

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.

21

get è una funzione che viene chiamata quando si tenta di leggere il valore player.health, come in:

console.log(player.health); 

E 'efficacemente non molto diverso da quello:

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

L'opposto di get è impostato , che verrebbe utilizzato quando si assegna il valore. Poiché non esiste un setter, sembra che l'assegnazione per la salute del giocatore non è intesa:

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

Un esempio molto semplice:

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

è proprio come una funzione che non è necessario utilizzare effettivamente '()' per chiamare ... Non capisco quale sia stata l'idea quando hanno inventato questa cosa. Le funzioni sono totalmente le stesse: https://jsbin.com/bugipi/edit?js,console,output – vsync

+0

Come posso una risposta preferita ??? –

2

In sostanza, defineProperty è un metodo che accetta 3 parametri: un oggetto, una proprietà e un descrittore. Quello che sta accadendo in questa particolare chiamata è la proprietà "health" dell'oggetto player che viene assegnata a 10 più 15 volte il livello dell'oggetto giocatore.

0

sì non più la funzione che si estende per l'impostazione setter & getter questo è il mio esempio Object.defineProperty (obj, nome, 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); 
0

Object.defineProperty() è una function..Its globale non disponibile all'interno della funzione che dichiara diversamente l'oggetto. Devi usarlo staticamente ...

7

defineProperty è un metodo su Object che consente di configurare le proprietà per soddisfare alcuni criteri S. Ecco un semplice esempio con un oggetto dipendente con due proprietà firstName & lastName e aggiunge le due proprietà sovrascrivendo il metodo toString sull'oggetto.

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

Si otterrà in uscita come: Jameel Moideen

ho intenzione di cambiare lo stesso codice utilizzando defineProperty sull'oggetto

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

Il primo parametro è il nome del oggetto e quindi secondo parametro è il nome della proprietà che stiamo aggiungendo, nel nostro caso è toString e quindi l'ultimo parametro è json object che ha un valore che diventerà una funzione e tre parametri scrivibili, enumerabili e co nfigurable.Right ora ho appena dichiarato tutto come vero.

Se u provare l'esempio si ottengono in uscita come: Jameel Moideen

Cerchiamo di capire il motivo per cui abbiamo bisogno dei tre proprietà quali scrivibile, enumerabile e configurabile. scrivibile Uno della parte molto fastidioso del JavaScript è, se si modifica la proprietà toString a qualcos'altro, ad esempio enter image description here

se correre di nuovo, tutto diventa pause Cambiamo scrivibile su false. Se esegui di nuovo lo stesso, otterrai l'output corretto come 'Jameel Moideen'. Questa proprietà impedirà di sovrascrivere questa proprietà in seguito. enumerable se si stampano tutte le chiavi all'interno dell'oggetto, è possibile visualizzare tutte le proprietà incluso toString.

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

enter image description here

se si imposta enumerabile su false, è possibile nascondere toString proprietà da tutti gli altri. Se eseguire di nuovo si otterrà nome, cognome configurabile

se qualcuno poi ridefinito l'oggetto in un secondo momento, per esempio enumerabile true ed eseguirlo. Puoi vedere la proprietà toString di nuovo.

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

è possibile limitare questo comportamento impostare configurabile su false.

Orginal reference of this information is from my personal Blog

+1

Ho capito che l'avevi nel tuo blog e l'hai incollato qui, ma almeno lo so per il futuro: gli screencaps non sono popolari su SO. Non puoi copiare il codice copypaste per provarlo e il codice non verrà visto dai motori di ricerca o dalla tecnologia di assistenza. –

+0

@JacqueGoupil Hai ragione. Aggiornerò con il codice add anziché con lo screenshot – JEMI

Problemi correlati