2012-12-27 13 views
27

Come sapete, possiamo definire getter e setter in JS utilizzando defineProperty(). Sono stato bloccato mentre cercavo di estendere la mia lezione usando defineProperty().JS defineProperty and prototype

Ecco un codice di esempio:

Ho un array di campi che deve essere aggiunto a un oggetto

fields = ["id", "name", "last_login"] 

Inoltre ho una classe che verrà modificato

var User = (function(){ 
    // constructor 
    function User(id, name){ 
     this.id  = id 
     this.name = name 
    } 
    return User; 
})(); 

E una funzione che aggiungerà campi alla classe usando defineProperty()

var define_fields = function (fields){ 
    fields.forEach(function(field_name){ 
     var value = null 
     Object.defineProperty(User.prototype, field_name, { 
      get: function(){ return value } 
      set: function(new_value){ 
       /* some business logic goes here */ 
       value = new_value 
      } 
     }) 
    }) 
}; 

Dopo l'esecuzione define_fields() ho i miei campi nel caso del User

define_fields(fields); 
user1 = new User(1, "Thomas") 
user2 = new User(2, "John") 

Ma i valori di queste proprietà sono identiche

console.log(user2.id, user2.name) // 2, John 
console.log(user1.id, user1.name) // 2, John 

C'è un modo per rendere il lavoro correttamente defineProperty() in questo caso? Se capisco il problema è con value che diventa identico per ogni istanza della classe, ma non riesco a capire come risolverlo. Grazie in anticipo per le vostre risposte.

UPD: In questo modo getta "RangeError: Dimensione massima stack di chiamate superato"

var define_fields = function (fields){ 
    fields.forEach(function(field_name){ 
     Object.defineProperty(User.prototype, field_name, { 
      get: function(){ return this[field_name] } 
      set: function(new_value){ 
       /* some business logic goes here */ 
       this[field_name] = new_value 
      } 
     }) 
    }) 
}; 
+2

Gotta love JavaScript. Un momento pensi di avere il peso di tutte le cose che puoi esprimere con esso, e poi ti dimostra orribilmente torto orribile. – Dmitry

+1

Devi avere paura di js in produzione. Non accetterò mai un lavoro per risolvere un'applicazione js che non ho scritto! – NoChance

risposta

14

sono giunto alla stessa conclusione Mikhail Kraynov tre minuti dopo ha risposto. Quella soluzione definisce nuove proprietà ogni volta che viene chiamato il costruttore. Mi chiedevo se, come hai chiesto, c'era un modo di mettere i getter e setter nel prototipo. Ecco quello che mi si avvicinò con:

var User = (function() { 
    function User (id, nam) { 
    Object.defineProperty (this, '__', // Define property for field values 
     { value: {} }); 

    this.id = id; 
    this.nam = nam; 
    } 

    (function define_fields (fields){ 
    fields.forEach (function (field_name) { 
     Object.defineProperty (User.prototype, field_name, { 
     get: function() { return this.__ [field_name]; }, 
     set: function (new_value) { 
       // some business logic goes here 
       this.__[field_name] = new_value; 
      } 
     }); 
    }); 
    }) (fields); 

    return User; 
})(); 

In questa soluzione definisco i getter e setter di campo nel prototipo, ma fare riferimento a un (nascosto) immobili in ogni istanza che detiene i valori di campo.

Vedere il violino qui: http://jsfiddle.net/Ca7yq

ho aggiunto un po 'di codice per il violino di mostrare alcuni effetti sulla enumerazione delle proprietà: http://jsfiddle.net/Ca7yq/1/

+0

È fantastico! Esattamente quello che mi serve :) –

+0

Prego. Questo ha l'effetto curioso che gli oggetti con lo stesso costruttore non abbiano necessariamente proprietà in comune. Un fatto interessante che deve sicuramente avere un'applicazione fantastica da qualche parte – HBP

+0

Terrò la mia app in segreto :) Più tardi verrà pubblicata nel Chrome Web Store e forse alcuni dei suoi codici sorgente saranno su GitHub. –

7

Mi sembra, che quando si defineProperties per prototipi, tutte le azioni istanze che le proprietà. Così la variante a destra potrebbe essere

var User = (function(){ 
// constructor 
function User(id, name){ 
    this.id  = id 
    this.name = name 

    Object.defineProperty(this, "name", { 
     get: function(){ return name }, 
     set: function(new_value){ 
      //Some business logic, upperCase, for example 
      new_value = new_value.toUpperCase(); 
      name = new_value 
     } 
    }) 
} 
return User; 
})(); 
+2

Sì, ho pensato a questa soluzione. Ma porterà alcuni altri problemi. Diciamo che ho un 'Model' di classe che è astratto e un' User' che è ereditato da 'Model'. Quindi ho bisogno di qualcosa che creerà automaticamente i metodi necessari specificamente per la classe 'User'. Non voglio scrivere definizioni di questi metodi ogni volta che creo un nuovo modello. Per esempio poco dopo avrò classi 'Album' e' Song' che saranno ereditate anche da 'Model'. –

+3

Non ti mangerebbe tutta la memoria? Creerà nuove funzioni per ogni istanza. –

4

Come si definiscono le proprietà sul prototipo oggetto di tutte le istanze degli utenti, tutti quegli oggetti condivideranno la stessa variabile value. Se questo non è ciò che si vuole, è necessario chiamare defineFields su ogni istanza utente a parte - nel costruttore:

function User(id, name){ 
    this.define_fields(["name", "id"]); 
    this.id  = id 
    this.name = name 
} 
User.prototype.define_fields = function(fields) { 
    var user = this; 
    fields.forEach(function(field_name) { 
     var value; 
     Object.defineProperty(user, field_name, { 
      get: function(){ return value; }, 
      set: function(new_value){ 
       /* some business logic goes here */ 
       value = new_value; 
      } 
     }); 
    }); 
}; 
+0

Questa caratteristica di condivisione è diversa dalle specifiche ES? Prima ho capito che ero molto confuso. – pllee

+1

Sì, il comportamento è specificato in ES5 (combinando le sezioni su prototipi, chiusure, ecc.) – Bergi

39

Si prega di non realizzare qualsiasi altra versione, perché si mangia tutta la vostra memoria nella vostra applicazione:

var Player = function(){this.__gold = 0}; 

Player.prototype = { 

    get gold(){ 
     return this.__gold * 2; 
    }, 



    set gold(gold){ 
     this.__gold = gold; 
    }, 
}; 

var p = new Player(); 
p.gold = 2; 
alert(p.gold); // 4 

se si istanze 10000 oggetti:

  • Con il mio metodo: avrete solo 2 funzioni in memoria;
  • Con gli altri metodi: 10000 * 2 = 20000 funzioni nella memoria;
+9

CHE COSA È QUESTO SINTASSI ?! PERCHÉ NON HO VISTO MAI QUESTO ?! – Qix

+1

@Qix et al vedi https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters http://devdocs.io/javascript/functions/get probabilmente documentato anche altrove. – jimmont

+0

Fantastico, grazie mille. Anche questo [velocizza i miei test] (http://umbrellajs.com/tests), che include test delle prestazioni. –

0

Dalla risposta accettata, mi rendo conto che ciò che stiamo cercando di fare qui è la definizione variabili di istanza privata. Queste variabili dovrebbero essere sull'istanza (this), piuttosto che sull'oggetto prototype. Normalmente chiamiamo le variabili private facendo precedere un underscore al nome della proprietà.

var Vehicle = {}; 
Object.defineProperty(Vehicle, "make", { 
    get: function() { return this._make; } 
    set: function(value) { this._make = value; } 
}); 

function Car(m) { this.make = m; } //this will set the private var _make 
Car.prototype = Vehicle; 

La risposta accettata mette in pratica tutte le variabili private in un contenitore, che in realtà è meglio.

+0

Hai perfettamente ragione. Le variabili locali (private) all'interno dell'ambito sono più efficienti e sicure. Ma c'è un'altra domanda: come definire le variabili dinamicamente? Voglio avere solo una classe, ad esempio 'Model' sul client e poi farò qualcosa come' MyCustomModel estende Model' e 'Model' gestirà la definizione delle proprietà su una particolare classe. Puoi prendere Rails ActiveModel come riferimento a ciò che voglio fare. –

+0

E 'eval' non è un buon modo per definire dinamicamente le variabili :) –

+0

Penso che tu stia cercando di fare qualche acrobazia Javascript :). Per le variabili locali penso che non abbiamo opzioni ma usare eval. In uno dei miei programmi lo faccio: var expr = ""; per (var nome in vars) expr + = "var" + name + "= vars [nome]; \ n"; eval (espressione); ... usa le vars locali. Non so come usarlo in un contesto ereditario però –

1

Questa soluzione è senza consumo di memoria extra. Il tuo codice aggiornato è vicino. Hai solo bisogno di usare this.props [field_name] invece di dirigerlo [nome_campo].

Si prega di notare che la chiamata defineProperty sostituito al Object.create

Js Fiddle http://jsfiddle.net/amuzalevskyi/65hnpad8/

// util 
function createFieldDeclaration(fields) { 
    var decl = {}; 
    for (var i = 0; i < fields.length; i++) { 
     (function(fieldName) { 
      decl[fieldName] = { 
       get: function() { 
        return this.props[fieldName]; 
       }, 
       set: function (value) { 
        this.props[fieldName] = value; 
       } 
      } 
     })(fields[i]); 
    } 
    return decl; 
} 

// class definition 
function User(id, name) { 
    this.props = {}; 
    this.id = id; 
    this.name = name; 
} 
User.prototype = Object.create(Object.prototype, createFieldDeclaration(['id','name'])); 

// tests 
var Alex = new User(0, 'Alex'), 
    Andrey = new User(1, 'Andrey'); 

document.write(Alex.name + '<br/>'); // Alex 
document.write(Andrey.name + '<br/>'); // Andrey 

Alex.name = "Alexander"; 
document.write(Alex.name + '<br/>'); // Alexander 
document.write(Andrey.name + '<br/>'); //Andrey 
Problemi correlati