【问题标题】:Implement javascript instance store by returning existing instance from constructor通过从构造函数返回现有实例来实现 javascript 实例存储
【发布时间】:2012-06-21 19:13:27
【问题描述】:

我正在尝试在 Backbone.js 中实现我的“实例存储”版本,正如 Soundcloud 在他们最近的博客文章中所描述的那样:

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

相关摘录:

为了解决这个问题,我们使用了一个我们称为实例存储的构造。此存储是一个对象,每次调用模型的构造函数时都会隐式访问和修改它。当第一次构建模型时,它会将自己注入到 store 中,使用它的 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 运算符调用构造函数时,返回的对象将被分配到赋值语句的左侧。这不会发生,即使构造函数返回一个对象,也会使用 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

我要注意的是,第二个中的属性对象包含其中包含的主干对象的所有方法,它们不应该是。它也没有id,我也不知道为什么。希望这能提供一些见解。谢谢。

【问题讨论】:

  • 您能给我们一个 MyModel 的调试视图吗?
  • 当然。原谅我的无知,但你能具体说明你需要什么吗?这里是JavaScript调试新手,我的调试一般由console.log语句组成。
  • 是的,那太好了。我猜它是一个函数,那么它的代码以及它可以访问的变量范围可能会很有趣。
  • 在评论中很难获得有用的调试信息,所以我将创建一个简单的测试文件并托管它并添加 URL。请给我几分钟。
  • 这是jsfiddle.net/N5tRZ/2的一个jsfiddle@

标签: javascript backbone.js


【解决方案1】:

我不会为此使用extend,我认为拥有一个单独的“工厂”是正确的想法。它将允许您扩展您的模型而不必担心副作用。

annotated source 主干用extend 做了一些奇怪的事情,我还没有完全理解它。 (另请查看inherits)所以让我们暂时跳过它并坚持使用您的工作解决方案。

我已经修改了您生成工厂模型的方法,您应该能够像普通模型一样使用它们(例如,将它们设置在集合上),除非扩展它们不起作用。他们还将像 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!

here's a fiddle 一起玩。

另外,我欢迎有人提出一个模型内解决方案,将“存储”保留在模型实例的原型中。此外,为了防止内存泄漏,我们可能应该在工厂或模型本身上创建引用计数销毁方法。

【讨论】:

【解决方案2】:

@wizard 的方法看起来非常漂亮和干净。对此 +1。

它在 SoundCloud 中的实现方式是通过重写 Backbone.Model.extend 方法来使用我们修改的构造函数和闭包中的存储创建一个类。 store 最初是在一个闭包中创建的,以保持类的接口干净,但一段时间后发现它对调试很有用,可以引用每个类的 store,所以它也附在了那里。

我们确实有一个引用计数,因此内存使用量不会爆炸,并且还让类能够定义一个自定义函数,该函数给出唯一值来识别它。大多数时候id 就足够了,但在某些极端情况下它并不完全奏效。

我欢迎有人提出一个模型内解决方案,将“存储”保留在模型实例的原型中

你可以myInstance.constructor.store

【讨论】:

  • 你见过幸运数字斯莱文吗?
【解决方案3】:

使用@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 并允许您扩展工厂:

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!

:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-13
    • 2016-09-16
    • 2015-10-01
    • 2017-02-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多