6

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 コンストラクターの "this" です。

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

2 番目の属性オブジェクトには、バックボーン オブジェクトのすべてのメソッドが含まれていることに注意してください。また、IDもありません。理由はわかりません。うまくいけば、これはいくつかの洞察を提供します。ありがとう。

4

3 に答える 3

2

@wizard のアプローチはかなり素晴らしく、きれいに見えます。それに+1。

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

メモリ使用量が爆発しないように参照カウントがあり、クラスを識別するための一意の値を与えるカスタム関数を定義する機能もクラスに与えます。ほとんどのid場合はこれで十分ですが、うまくいかないケースもあります。

モデルインスタンスのプロトタイプに「ストア」を保持するモデル内ソリューションを考え出す人を歓迎します

あなたができるmyInstance.constructor.store

于 2012-06-22T18:00:19.370 に答える
0

@reconbot ソリューションを使用した後、instanceof 演算子が壊れていることがわかりました。

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

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

モデル独自の id プロパティを (idAttribute 経由で) 使用し、instanceof と連携してファクトリを拡張できる新しいバージョンを開発しました。

フィドル

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!

:)

于 2012-12-20T10:34:21.857 に答える