2012-06-21 5 views
6

Sto cercando di implementare la mia versione della "Instance Store" in Backbone.js come descritto da Soundcloud nella loro recente post sul blog:Implementare javascript negozio di esempio restituendo un'istanza esistente dal costruttore

http://backstage.soundcloud.com/2012/06/building-the-next-soundcloud/

Rilevante Estratto:

Per risolvere questo, utilizziamo un costrutto che chiamiamo l'archivio di istanze. Questo negozio è un oggetto a cui si accede e si modifica in modo implicito ogni volta che viene chiamato un costruttore per un modello. Quando un modello viene costruito per la prima volta, si inietta nello store, utilizzando il suo id come chiave univoca. Se lo stesso costruttore di modello viene chiamato con lo stesso id, viene restituita l'istanza originale.

var s1 = new Sound({id: 123}), 
    s2 = new Sound({id: 123}); 

s1 === s2; // true, these are the exact same object. 

Questo funziona a causa di una caratteristica sorprendentemente poco conosciuta di Javascript. Se un costruttore restituisce un oggetto, allora questo è il valore assegnato. Pertanto, se restituiamo un riferimento all'istanza creata in precedenza, otteniamo il comportamento desiderato. Dietro le quinte, il costruttore è fondamentalmente facendo questo:

var store = {}; 

function Sound(attributes) { 
    var id = attributes.id; 

    // check if this model has already been created 
    if (store[id]) { 
     // if yes, return that 
     return store[id]; 
    } 
    // otherwise, store this instance 
    store[id] = this; 
} 

ho implementato la mia versione di questo sovrascrivendo la classe Backbone.Model di creare il mio costruttore.

var MyModel = Backbone.Model.extend({ 
    constructor: function (attributes, options) { 
     var id = attributes ? attributes.id : undefined; 

     if (this.store[id]) { 
      return this.store[id]; 
     } 

     Backbone.Model.prototype.constructor.apply(this, arguments); 

     if (id) { 
      this.store[id] = this; 
     } 
    } 
}); 

var MyOtherModel = MyModel.extend({ 
    store: {}, 

    //other model stuff 
}); 

Questo stava funzionando bene, ma qualcosa deve essere cambiato e ora è smesso di funzionare, e io sono sicuro perché. Le istanze appena create sono archiviate nell'oggetto del negozio senza alcun problema: ogni classe che estende la classe MyModel ha il proprio archivio vuoto per evitare collisioni di istanze di un tipo diverso con lo stesso id. L'istanza corretta viene anche recuperata senza problemi quando il costruttore viene chiamato con un ID esistente, tuttavia quando vengono restituiti dal costruttore il valore di ritorno viene ignorato. La mia comprensione dalle specifiche è che i costruttori possono restituire un oggetto - ma non una primitiva - e l'oggetto restituito verrà assegnato alla parte sinistra dell'istruzione di assegnazione quando il costruttore viene chiamato con il nuovo operatore. Ciò non sta accadendo, anche se il costruttore restituisce un oggetto, viene utilizzato l'oggetto vuoto creato dal nuovo operatore.

Alcune informazioni di debug. Non sei sicuro di quanto saranno utili queste informazioni. Questo è "questo" nel costruttore MyModel per un oggetto che viene istanziato per la prima volta.

child 
    _callbacks: Object 
    _escapedAttributes: Object 
    _previousAttributes: Object 
    _setting: false 
    attributes: Object 
     id: "4fd6140032a6e522f10009ac" 
     manufacturer_id: "4f4135ae32a6e52a53000001" 
     name: "Tide" 
     uniqueName: "tide" 
    __proto__: Object 
    cid: "c50" 
    collection: child 
    id: "4fd6140032a6e522f10009ac" 
    __proto__: ctor 
     constructor: function(){ parent.apply(this, arguments); } 
     defaults: Object 
     store: Object 
     url: function() { 
     urlRoot: function() { 
     __proto__: ctor 

E questo è "questo" nel costruttore MyModel quando è un oggetto che viene restituito dal negozio esempio:

child 
    _callbacks: Object 
    _escapedAttributes: Object 
    _previousAttributes: Object 
    _setting: false 
    attributes: Object 
     _validate: function (attrs, options) { 
     bind: function (events, callback, context) { 
     change: function (options) { 
     changedAttributes: function (diff) { 
     clear: function (options) { 
     clone: function() { 
     constructor: function(){ parent.apply(this, arguments); } 
     defaults: Object 
     destroy: function (options) { 
     escape: function (attr) { 
     fetch: function (options) { 
     get: function (attr) { 
     has: function (attr) { 
     hasChanged: function (attr) { 
     idAttribute: "id" 
     initialize: function(){} 
     isNew: function() { 
     isValid: function() { 
     manufacturer_id: 0 
     name: "" 
     off: function (events, callback, context) { 
     on: function (events, callback, context) { 
     parse: function (resp, xhr) { 
     previous: function (attr) { 
     previousAttributes: function() { 
     save: function (key, value, options) { 
     set: function (key, value, options) { 
     store: Object 
     toJSON: function() { 
     trigger: function (events) { 
     unbind: function (events, callback, context) { 
     unset: function (attr, options) { 
     url: function() { 
     urlRoot: function() { 
     __proto__: Object 
     cid: "c141" 
    __proto__: ctor 
     constructor: function(){ parent.apply(this, arguments); } 
     defaults: Object 
     store: Object 
     url: function() { 
     urlRoot: function() { 
     __proto__: ctor 

Cosa faccio notare è che gli attributi oggetto nel secondo si ha tutto il i metodi di un oggetto backbone incluso, che non dovrebbero essere. Inoltre non ha id, di nuovo non sono sicuro del perché. Speriamo che questo fornisca qualche intuizione. Grazie.

+0

Potrebbe darci un deb ug view of MyModel? – Bergi

+0

Sicuro. Scusa la mia ignoranza ma puoi specificare esattamente ciò di cui hai bisogno? JavaScript debugging qui, il mio debugging è generalmente costituito da istruzioni console.log. – Kareem

+0

Sì, sarebbe fantastico. Suppongo che sia una funzione, quindi il suo codice e forse gli ambiti variabili a cui ha accesso potrebbero essere interessanti. – Bergi

risposta

3

Non userei l'estensione per questo, penso che avere una "fabbrica" ​​separata sia l'idea giusta. Ti consentirà di estendere i tuoi modelli senza timori di effetti collaterali.

Dal backbone annotated source fa alcune cose strane con estensione, non ho ancora avvolto la mia testa intorno ad esso. (Controlla anche inherits) Quindi, per ora, saltalo e rimani con la tua soluzione di lavoro.

Ho modificato il metodo per generare modelli di fabbrica, dovresti essere in grado di usarli come i modelli normali (ad esempio, impostarli su una raccolta) tranne che per estenderli non funzionerà.Gestiranno inoltre l'aggiornamento dei modelli con dati nuovi come l'esempio di soundcloud.

var makeStoreable = function(model){ 
    var StoreModel = function(attr, opt){ 
    if(!attr || !attr.id){ 
     // The behavior you exhibit here is up to you 
     throw new Error('Cool Models always have IDs!'); 
    } 
    if(this.store[attr.id]){ 
     this.store[attr.id].set(attr, opt); 
    }else{ 
     var newModel = new model(attr, opt); 
     this.store[attr.id] = newModel; 
    } 
    return this.store[attr.id]; 
    }; 
    StoreModel.prototype.store = {}; 
    return StoreModel; 
}; 

var CoolModel = Backbone.Model.extend({}); 

CoolModel = makeStoreable(CoolModel); 

var a = new CoolModel({ 
    id: 4, 
    coolFactor: 'LOW' 
}); 

var b = new CoolModel({ 
    id:4, 
    coolFactor: 'HIGH' 
}); 

console.log(a===b); //true! 
console.log(a.get('coolFactor') === 'HIGH'); //true! 

E here's a fiddle con cui giocare.

Inoltre, darei il benvenuto a qualcuno per trovare una soluzione in modello che tenga il "negozio" nel prototipo delle istanze del modello. Inoltre, per evitare perdite di memoria, dovremmo probabilmente creare un metodo di conteggio dei riferimenti di distruzione, sia in fabbrica che nel modello stesso.

+0

Sto lavorando alla soluzione che ho chiesto per https://github.com/reconbot/backbone-singleton – reconbot

+0

Ok ho finito, usa la mia soluzione. – reconbot

+0

E l'ha aggiornato per funzionare con il backbone più recente – reconbot

2

@ l'approccio del mago sembra carino e pulito. +1 a quello.

Il modo in cui è implementato in SoundCloud è sovrascrivere il metodo Backbone.Model.extend per creare una classe con il nostro costruttore modificato e lo store in una chiusura. Il negozio è stato originariamente creato in una chiusura per mantenere l'interfaccia della classe pulita, ma dopo un po 'è stato trovato utile per il debug per avere un riferimento allo store di ogni classe, quindi è stato anche allegato lì.

Abbiamo un conteggio dei riferimenti in modo che l'utilizzo della memoria non esploda e diamo anche alle classi la possibilità di definire una funzione personalizzata che fornisce il valore univoco per identificarla. Il più delle volte id è sufficiente, ma ci sono alcuni casi d'angolo in cui non funziona del tutto.

sarei il benvenuto a qualcuno di trovare una soluzione di modello a mantenere il "negozio" in prototipo di casi modello

Si potrebbe fare myInstance.constructor.store

+0

Hai mai visto il numero fortunato Slevin? –

+0

Ti dispiacerebbe condividere un po 'del codice? :) –

0

Dopo soluzione Uso @reconbot I' ho trovato che brokes l'operatore instanceof:

(new CoolModel) instanceof CoolModel // FALSE!!! 

E

var MyModel = Backbone.Model.extend({ 
    idAttribute: 'myId' 
}); 
new MyModel({ myId: 1 }) === new MyModel({ myId: 1 }) // FALSE! 

Ho sviluppato una nuova versione che usa proprietà id del modello (via idAttribute) e lavora con instanceof e consente di estendere la fabbrica:

FIDDLE

function makeStoreable(model) { 
    var store = {}; 
    var idField = model.prototype.idAttribute; 

    function ModelFactory(attr, opt) { 
     if (!attr || !(idField in attr)) { 
      throw new Error('Cool Models always have IDs!'); 
     } 

     var id = attr[idField]; 

     if (store.hasOwnProperty(id)) { 
      store[id].set(attr, opt); 
     } else { 
      model.call(this, attr, opt); 
      store[id] = this; 
     } 

     return store[id]; 
    } 

    function intermediate() {} 
    intermediate.prototype = model.prototype; 
    ModelFactory.prototype = new intermediate; 

    // Only EcmaScript5! 
    // ModelFactory.extend = model.extend.bind(model); 
    ModelFactory.extend = function() { 
     return model.extend.apply(model, arguments); 
    }; 

    return ModelFactory; 
} 

E il test:

var RareID = Backbone.Model.extend({ 
    idAttribute: '_myOwnServerId' 
}); 
RareID = makeStoreable(RareID); 

var a = new RareID({ 
    _myOwnServerId: 4, 
    coolFactor: 'LOW' 
}); 

var b = new RareID({ 
    _myOwnServerId: 4, 
    coolFactor: 'HIGH' 
}); 

console.log(a===b); //true! 
console.log(a instanceof RareID); //true! 
console.log(a.get('coolFactor') === 'HIGH'); //true! 

:)

Problemi correlati