2012-06-21 3 views
6

から既存のインスタンスを返すことによって、JavaScriptのインスタンスストアを実装し、私は彼らの最近のブログ記事でてSoundcloudで説明したようBACKBONE.JSで「インスタンスストア」の私のバージョンを実装しようとしています:はコンストラクタ

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

関連抜粋:

これを解決するために、インスタンスストアと呼ばれる構造を使用します。このストアは、モデルのコンストラクタが呼び出されるたびに暗黙的にアクセスされ、変更されるオブジェクトです。モデルが初めて構築されると、そのモデルを固有のキーとしてIDを使用してストアに注入します。同じモデルコンストラクタが同じIDで呼び出された場合、元のインスタンスが返されます。

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

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

これはJavascriptの驚くほど知られていない機能のために動作します。コンストラクタがオブジェクトを返す場合、それは割り当てられた値です。したがって、前に作成したインスタンスへの参照を返すと、目的の動作が得られます。舞台裏では、コンストラクタは、基本的にこれをやっている:

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; 
} 

私は自分のコンストラクタを作成するためにBackbone.Modelクラスをオーバーライドすることによって、この私のバージョンを実装しました。

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

これはうまくいきましたが、何かが変更されている必要があり、現在は動作が停止しています。新しく作成されたインスタンスは問題なしでストアオブジェクトに格納されます.MyModelクラスを拡張する各クラスは、同じIDを持つ異なる型のインスタンスの衝突を避けるために独自の空のストアを持っています。正しいインスタンスは、コンストラクターが既存のIDで呼び出されたときに問題なく検索されますが、コンストラクターから返されるときには戻り値は無視されます。仕様から私の理解は、コンストラクタが新しいオペレータで呼び出されたときに、コンストラクタがオブジェクトを返すことができますが、プリミティブは返すことができず、返されたオブジェクトは代入文の左辺に割り当てられます。これは起こらず、たとえコンストラクタがオブジェクトを返すとしても、new演算子で作成された空のオブジェクトが使用されます。

一部のデバッグ情報。この情報がどの程度役立つかはわかりません。これは、初めてインスタンス化されるオブジェクトのMyModelコンストラクタ内の「this」です。

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 

そして、これは「これは」mymodelというコンストラクタで、それは、インスタンスストアから返されるオブジェクトのときである:私は注意して何

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 

は属性が二番目にオブジェクトということですしているすべてのそれらに含まれているバックボーンオブジェクトのメソッドです。それにもIDはありませんが、私は再びなぜわかりません。うまくいけば、これはいくつかの洞察を提供します。ありがとう。

+0

あなたは私たちにDEBを与えることができますMyModelの眺めは? – Bergi

+0

私の無知を許しても、あなたが必要とするものを正確に指定することはできますか? JavaScriptデバッギング初心者は、私のデバッグは一般的にconsole.logステートメントで構成されています。 – Kareem

+0

はい、それは素晴らしいでしょう。私はそれが関数であり、そのコードと多分それがアクセスできる変数スコープが面白いと思います。 – Bergi

答えて

3

私はこのために延長を使用しません、私は別の "工場"が正しいアイデアだと思う。副作用の恐れなしにモデルを拡張することができます。

annotated sourceから、バックボーンには拡張機能のある奇妙なものがありますが、私はそれを周りに頭を包んでいません。 (inheritsもチェックしてください)今のところそれをスキップして、あなたの実際の解決策に固執しましょう。

私は工場モデルを生成するためにあなたのメソッドを変更しました。通常のモデルのように使用することができます(例えば、コレクションに設定するなど)。また、サウンドクラウドの例のような新鮮なデータでモデルを更新することもできます。

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! 

そしてhere's a fiddleと一緒に遊ぶ。

また、モデルインスタンスのプロトタイプに「ストア」を保持したモデルソリューションをお試しください。また、メモリリークを防ぐために、おそらくファクトリまたはモデル自体のどちらかに参照を数えるdestroyメソッドを作成する必要があります。

+0

私はhttps://github.com/reconbot/backbone-singletonのために尋ねた解決策に取り組んでいます – reconbot

+0

いいえ、私の解決策を使いました。 – reconbot

+0

最新のバックボーンで動作するようにアップデートしました – reconbot

2

@ wizardのアプローチは非常に素晴らしく清潔です。 +1する。

SoundCloudで実装されている方法は、Backbone.Model.extendメソッドをオーバーライドして、変更されたコンストラクタとストアをクロージャで作成することです。ストアはもともとクラスのインターフェイスをきれいに保つためにクロージャで作成されましたが、しばらくしてデバッグが各クラスのストアへの参照を持つのに便利であることが判明しました。

メモリ使用量が爆発的にならないように参照カウントがあります。クラスには、それを識別するための一意の値を与えるカスタム関数を定義する機能もあります。ほとんどの場合idで十分ですが、うまくいかないコーナーケースがあります。私はあなたが私が「使用@reconbot液後myInstance.constructor.store

+0

あなたはラッキーナンバースリービンを見たことがありますか? –

+0

少しのコードを共有してもよろしいですか? :) –

0

を行うことができモデルインスタンスのプロトタイプ

に「ストア」を維持するモデル溶液中で誰かが思い付くことを歓迎したい

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

そして

:それはinstanceof演算子をbrokes見つけまし
var MyModel = Backbone.Model.extend({ 
    idAttribute: 'myId' 
}); 
new MyModel({ myId: 1 }) === new MyModel({ myId: 1 }) // FALSE! 

(idAttribute経由)モデルの独自のIDプロパティを使用してのinstanceofで動作し、あなたが工場拡張することができます新しいバージョンIを開発しました:

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; 
} 

とテストを:

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! 

:)

関連する問題