【问题标题】:javascript objects - retrieve child class variablejavascript 对象 - 检索子类变量
【发布时间】:2025-11-30 04:40:01
【问题描述】:

问题底部有一个javascriptcoffescript jsfiddle。

两个小提琴都包含需要按特定顺序读取的解释性 cmets,当您单击 product 或 @ 时,它将值打印到 console 987654329@,另外我给你这个我的问题的基本解释。

  • 我有 3 个 Javascript 类 PurchaseProductItem
  • 一个 Purchase 有很多Products一个 Product 有很多Items
  • Purchase 对象在$('submit') 上设置了一个click event handleronClick() 会将items 数据发布到我的后端api
  • 这是从my backend api 接受的data 格式

    {
      'purchase' => {
        'items_attributes' => {
          '0' => {
            'purchase_id' => '1'
          },
          '1' => {
            'purchase_id' => '2'
          }
        }
      }
    }
    

My coffeescript jsfiddle is at the following link

点击下方打开javascript fiddle

(function() {
  var Item, Product, Purchase,
    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  Purchase = (function() {
    function Purchase() {
      /* on $(document).ready a new Purchase is created */ 
      this.submit = $('#submit');
      /* for each div.product a new Product instance is created */
      this.products = $.map($('.product'), function(product, i) {
        return new Product(product);
      });
      / @onSubmit() */
      
      /* Comment 3) 
      My issue here is how to I access the this.items from the Purchase class and call serialize()?
      onSubmit: function () {
        @submit.click(function(){console.log(Product.serialize())};
      }     */
    }

    return Purchase;

  })();

  Product = (function() {
    Product.items = [];

    function Product(product) {
      this.product = $(product);
      this.id = this.product.data("id");
      this.submit = $('#submit');
      this.setEvent();
      this.onSubmit();
    }

    Product.prototype.setEvent = function() {
      return this.product.click((function(_this) {
        return function() {
          /* Comment 1)
             Product.items is a class variable of Product, because I need to access it from the Purchase class and send post request. When the user clicks on the $('submit') button*/
          Product.items.push(new Item(_this.id));
          return console.log(Product.items);
        };
      })(this));
    };

    Product.prototype.onSubmit = function() {
      return this.submit.click(function() {
      /* Comment 2) 
      This works as you can see, but we have 4 products and this operation will 
      be performed 4 times. I want to achieve this in the Purchase object so it is perfomed only once, by creating a sumit event handler in Purchase */      
        return console.log(Product.serialize());
      });
    };

    Product.serialize = function() {
      var item;
      return {
        items_attributes: (function() {
          var j, len, ref, results;
          ref = Product.items;
          results = [];
          for (j = 0, len = ref.length; j < len; j++) {
            item = ref[j];
            results.push(item.serialize());
          }
          return results;
        })()
      };
    };

    return Product;

  })();

  Item = (function() {
    function Item(product_id) {
      this.product_id = product_id;
      this.serialize = bind(this.serialize, this);
    }

    Item.prototype.serialize = function() {
      return {
        product_id: this.product_id.toString()
      };
    };

    return Item;

  })();

  $(document).ready(function() {
    return new Purchase();
  });

}).call(this);
.console {
  background-color: grey;
  color: white;
  height: 500px;
}      # I print to the console Product.items 

h4 {
  color: red;
  width: 100%;
  text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<ul>
  <li class="product" data-id="1">Product 1</li>
  <li class="product" data-id="2">Product 2</li>
  <li class="product" data-id="3">Product 2</li>
  <li class="product" data-id="4">Product 3</li>
  <li class="product" data-id="5">Product 4</li>
  <div id="submit">Create Purchase</div>
</ul>

<h4>check logs by opening the console</h4>

在我编写开源代码时,您可以查看我的 commit historyspecific commit 并 fork 项目

【问题讨论】:

  • 您确定要使用字符串键"0""1" 的对象而不是数组吗?您的代码中的 @products.click 处理程序在哪里?你是说@submit.click 吗?
  • @Caffeinated.tech 非常感谢!我正在考虑在Purchase 类中执行类似@products.click =&gt; new Item 的操作,然后将这些实例保存在class Purchase; @items = ... 中,这样当我执行@submit.click 时,我可以通过传递所有@items 来执行AJAXrails params 要求的格式是 items_attributes =&gt; {"0".. 等等。但是你认为我可以使用数组吗?因为它需要被字符串化,以及如何使用该格式?非常感谢

标签: javascript ajax coffeescript javascript-objects


【解决方案1】:

我是 Active Model Serializer gem 的粉丝,它现在是 Rails 的一部分。我会尝试通过向所有类添加一个序列化方法来将此模式扩展到您的咖啡脚本中,并在您将数据传递到服务器时调用这些方法。

我不确定你对 Item 类的计划,所以这里是一个简单的模型,带有建议的 serialize 方法:

class Item
  constructor: (@purchase, @product, @quantity) ->

  serialize: =>
    purchase_id: @purchase.id.toString()
    product_id: @product.id.toString()
    quantity: parseInt(@quantity)

假设您的购买类将有一个@items 数组,那么Purchaseserialize 方法将如下所示:

serialize: =>
  items_attributes: (item.serialize() for item in @items)

然后您的 ajax 帖子将使用 serialize 方法:

$.ajax
   url: "/items"
   method: "POST"
   dataType: "json"
   data: 
     purchase: @serialize()
   error: (jqXHR, textStatus, errorThrown) ->
   success: (data, textStatus, jqXHR) ->

那么你应该得到一个

的 JSON 帖子正文
'purchase' => {
  'items_attributes' => [
    {
      'purchase_id' => '1'
    },
    {
      'purchase_id' => '2'
    }
  ]
}

您可以通过强参数在 Rails 控制器中使用:

params.require(:purchase).permit(item_attributes: [:purchase_id])

【讨论】:

  • 感谢您的回答。考虑到我需要学习 Javascript/Coffescript 并更好地理解这种编程语言,它帮助我更好地理解了这个问题。我决定为这个问题发布 200 个赏金(我对 javascript 和 coffescript 都进行了审查)。我虽然通知你。非常感谢
  • @FabrizioBertoglio 您应该发布一个关于您的数据结构的新问题,而不是编辑和替换原始问题。现在我的回答对以后遇到类似问题的任何人都没有用,而且似乎没有用
  • 我仍然可以给你 200 的赏金,问题的咖啡脚本版本仍然可以在 JSFiddle jsfiddle.net/b1tqkh00/1179 上找到,我已经按照 @Munim Munna 的建议实施了解决这个问题的方法。你可以看到我的提交历史,它清楚地解释了我实现了什么 github.com/fabriziobertoglio1987/sprachspiel/commits/… 该功能工作正常,但可以发布和奖励更好的 Coffescript 解决方案。如果您希望一旦赏金结束,我将删除 Javascript 小提琴并用 coffescript 代码替换它
  • 这是实现此问题的解决方案github.com/fabriziobertoglio1987/sprachspiel/commit/… 的具体提交从干燥的角度来看,我喜欢你的问题,在@Munim Munna 给我这个解决方案之前,我确实提交了上述解决方案,但是他很友好地向我解释说我可以在Purchase 类中使用Product.items,从而改进我的代码。我欠你们俩的债。
【解决方案2】:

您可以简单地将事件绑定到您的 Purchase 对象初始化时。

this.submit.click(function() {
    return console.log(Product.serialize());
});

工作片段:我已经注释掉了onSubmitProduct

(function() {
  var Item, Product, Purchase,
    bind = function(fn, me) {
      return function() {
        return fn.apply(me, arguments);
      };
    };

  Purchase = (function() {
    function Purchase() {
      /* on $(document).ready a new Purchase is created */
      this.submit = $('#submit');
      /* for each div.product a new Product instance is created */
      this.products = $.map($('.product'), function(product, i) {
        return new Product(product);
      });
      / @onSubmit() */

      /* Comment 3) 
      My issue here is how to I access the this.items from the Purchase class and call serialize()?
      onSubmit: function () {
        @submit.click(function(){console.log(Product.serialize())};
      }     */
      this.submit.click(function() {
        return console.log(Product.serialize());
      });
    }

    return Purchase;

  })();

  Product = (function() {
    Product.items = [];

    function Product(product) {
      this.product = $(product);
      this.id = this.product.data("id");
      this.submit = $('#submit');
      this.setEvent();
      // this.onSubmit();
    }

    Product.prototype.setEvent = function() {
      return this.product.click((function(_this) {
        return function() {
          /* Comment 1)
             Product.items is a class variable of Product, because I need to access it from the Purchase class and send post request. When the user clicks on the $('submit') button*/
          Product.items.push(new Item(_this.id));
          return console.log(Product.items);
        };
      })(this));
    };

    // Product.prototype.onSubmit = function() {
    //   return this.submit.click(function() {
    //     /* Comment 2) 
    //     This works as you can see, but we have 4 products and this operation will 
    //     be performed 4 times. I want to achieve this in the Purchase object so it is perfomed only once, by creating a sumit event handler in Purchase */
    //     return console.log(Product.serialize());
    //   });
    // };

    Product.serialize = function() {
      var item;
      return {
        items_attributes: (function() {
          var j, len, ref, results;
          ref = Product.items;
          results = [];
          for (j = 0, len = ref.length; j < len; j++) {
            item = ref[j];
            results.push(item.serialize());
          }
          return results;
        })()
      };
    };

    return Product;

  })();

  Item = (function() {
    function Item(product_id) {
      this.product_id = product_id;
      this.serialize = bind(this.serialize, this);
    }

    Item.prototype.serialize = function() {
      return {
        product_id: this.product_id.toString()
      };
    };

    return Item;

  })();

  $(document).ready(function() {
    return new Purchase();
  });

}).call(this);
.console {
  background-color: grey;
  color: white;
  height: 500px;
}

h4 {
  color: red;
  width: 100%;
  text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<ul>
  <li class="product" data-id="1">Product 1</li>
  <li class="product" data-id="2">Product 2</li>
  <li class="product" data-id="3">Product 2</li>
  <li class="product" data-id="4">Product 3</li>
  <li class="product" data-id="5">Product 4</li>
  <button type="button" id="submit">Create Purchase</button>
</ul>

<h4>check logs by opening the console</h4>

【讨论】:

  • 好的,所以我可以在Purchase 对象中调用Product.serialize() ?!我确实解决了这个github.com/fabriziobertoglio1987/sprachspiel/commit/…,但将items 保存为Purchase.items 类变量,但你的解决方案很干而且更好,所以我会在接下来的几天里尝试这个并让你知道。我仍在使用很多@Caffeinated.tech 解决方案,所以我需要了解谁帮助了我更多并给了他赏金.. 非常感谢!
  • 是的,您可以访问它,因为它在外部范围内被声明为变量。阅读 this 以更好地理解 JavaScript 作用域,理解作用域将使您在学习 JavaScript 方面处于领先地位。
  • 是的。我相信你是对的。这是解决方案的关键,了解这些对象是如何连接的,同时我仍然想知道如何从@product 实例访问@purchase。这也是关于scopes 还是我应该在new Product(@purchase) 构造函数中将@purchase 变量作为输入发送..?非常感谢..这是我理解这个问题的关键
  • 如果你只有一个 purchase 对象,那么范围是可以的,如果你有多个,那么你需要链接 productspurchase 只是你的想法, 和 purchase 应该包含一个产品列表。如果您两次单击同一项目,它们将被列出两次,您需要避免它。如果遇到困难,请尝试并发布问题更新。
【解决方案3】:

我已经根据代码 cmets 中的问题更新了您的咖啡脚本小提琴。

这是我的updated version

我已经更改了您的类结构,因此不需要任何静态变量,在这种情况下,这似乎是一种绕过糟糕设计的技巧。

您已将模型结构创建为:

  • 一次购买有很多产品
  • 一个产品有很多项目

但您的帖子数据格式要求表明:

  • 一次购买有很多商品
  • 一件商品属于一件商品(通过引用 id)

为了解决这种不一致,我将产品中的序列化数据展平,以便 items_attributes 是序列化项目对象的数组:

class Purchase
  ...
  serialize: =>
    items = (product.serialize() for product in @products)
    # flatten array of array of items:
    items_attributes: [].concat.apply([], items)

这条看起来很神秘的线 [].concat.apply([], items) 是扁平化嵌套数组的一层深度的简写(取自 answer)。

现在,product 的每个实例都将一组项目保存在自身上,而不是静态地保存在类上。

class Product  
  constructor: (product) ->
    @product = $(product)
    @id = @product.data("id")
    @submit = $('#submit')
    @items = []
    @registerEvents()

  addItem: =>
    console.log "added item #{@id}"
    @items.push new Item(@id) 

  registerEvents: ->
    @product.click @addItem

  serialize: =>
    (item.serialize() for item in @items)

我认为对此类结构进行更好的重新设计是删除ProductItem 类,因为只有一个产品ID,据我所知,项目就像一个计数器购买了许多单位的产品。您可以在产品上保留一个整数值,而不是为此设置一个类:

作为fiddle

class Purchase
  constructor: () -> 
    # on $(document).ready a new Purchase is created
    @submit = $('#submit')
    # for each div.product a new Product instance is created
    @products = $.map $('.product'), (product, i) -> 
      new Product(product)
    @registerEvents()

  onSubmit: => 
    console.log "send to server..."
    console.log JSON.stringify(@serialize(), null, 2)

  registerEvents: -> 
    @submit.click @onSubmit

  serialize: =>
    items_attributes: (product.serialize() for product in @products when product.amount isnt 0)

class Product  
  constructor: (product) ->
    @product = $(product)
    @id = @product.data("id")
    @submit = $('#submit')
    @amount = 0
    @registerEvents()

  addItem: =>
    console.log "added item #{@id}"
    @amount++

  registerEvents: ->
    @product.click @addItem

  serialize: =>
    product_id: @id
    amount: @amount

现在的输出看起来不同了,但更清晰:

新:

{
  "items_attributes": [
    {
      "product_id": 1,
      "amount": 1
    },
    {
      "product_id": 2,
      "amount": 3
    }
  ]
}

旧:

{
  "items_attributes": [
    {
      "product_id": "1"
    },
    {
      "product_id": "2"
    },
    {
      "product_id": "2"
    },
    {
      "product_id": "2"
    }
  ]
}

但这可能不适用于您当前的后端实现,具体取决于当前处理重复项的方式,因此如果无法更改任何遗留约束,请忽略最后一部分。


最后,我想补充一点,这种将事件侦听器和逻辑附加到 DOM 的“面向对象”方法比在加载时执行的典型 jquery 函数更结构化。但我过去使用过它,同时保持 DOM 结构和代码更新是一件很痛苦的事情,而且由于代码更改没有反映在另一个上,经常会导致错误。

作为替代方案,我强烈建议查看reactjs 或类似的DOM 抽象类型库。这些允许您将您的逻辑强耦合到它们所依赖的视图元素。

虽然通常与 JSX 一起使用,但它与 Coffeescript 结合得很好,但是这方面的资源很少。 Arkency 写了一篇关于react + coffeescript 的好博客,我也写了一篇短文comparing coffeescript to jsx

【讨论】: