Javascript 组合优于继承,在视图中添加附加功能而不依赖继承的更好方法是什么

Javascript 组合优于继承,在视图中添加附加功能而不依赖继承的更好方法是什么,javascript,inheritance,backbone.js,composition,Javascript,Inheritance,Backbone.js,Composition,过去我读过很多关于可组合性而非继承性的书,我完全相信这一概念,并在我的代码中大量使用了这一原则 然而,我在日常工作中遇到了一些问题,继承倾向于潜入视图,我很难看到如何实现更可组合的东西(我在日常工作中使用主干这一事实没有帮助)。当我想使用现有主干视图的所有功能,同时在顶部添加一些附加功能时,就会出现这种情况 以这个假设的例子为例,我们有一个电子商务类型的页面,其中有多个产品视图,每个视图代表一个特定产品的一系列可供选择的选项: var ProductView = (function(Backbo

过去我读过很多关于可组合性而非继承性的书,我完全相信这一概念,并在我的代码中大量使用了这一原则

然而,我在日常工作中遇到了一些问题,继承倾向于潜入视图,我很难看到如何实现更可组合的东西(我在日常工作中使用主干这一事实没有帮助)。当我想使用现有主干视图的所有功能,同时在顶部添加一些附加功能时,就会出现这种情况

以这个假设的例子为例,我们有一个电子商务类型的页面,其中有多个
产品
视图,每个视图代表一个特定产品的一系列可供选择的选项:

var ProductView = (function(Backbone, JST) {
  'use strict';

  return Backbone.View.extend({
    className: 'product',
    template: JST['application/templates/product']

    initialize: function(options) {
      this.options = options || {};
      this.collection.fetch();
      this.listenTo(this.collection, 'loaded', this.render);
    },

    render: function() {
      this.$el.html(
        this.template(this.collection)
      );

      return this;
    },
  }, {
    create: function(el) {
      var endpoint = '/api/options/' + el.getAttribute('data-basket-id') + '/' + el.getAttribute('data-product-id');

      new ProductView({
        el: el,
        collection: new ProductCollection(null, { url: endpoint })
      });
    }
  });
})(Backbone, JST);
假设我们想展示一些产品,这些产品要求访客收到一个确认框的提示(比如说,出于保险原因,这个特定的产品必须有保险出售,所以我们需要在用户将其添加到购物篮时提示用户):


有没有一种更具组合性的写作方式?或者,在这种情况下,通过继承中断是正确的吗?

对于这种特定情况,继承很有效。关于可组合性而非继承性的争论是徒劳的,使用最适合当前情况的方法

但是,仍然有一些改进可以用来简化继承。当我创建一个我要继承的主干类时,我会尝试使它在子类中不可见

实现这一点的一种方法是将父函数的初始化放在构造函数中,将
initialize
函数全部留给子函数。对于
事件
散列也是如此

var ProductView = Backbone.View.extend({
    className: 'product',
    template: JST['application/templates/product'],
    events: {},

    constructor: function(options) {
        // make parent event the default, but leave the event hash property
        // for the child view
        _.extend({
            "click .example-parent-event": "onParentEvent"
        }, this.events);

        this.options = options || {};
        this.collection.fetch();
        this.listenTo(this.collection, 'loaded', this.render);

        ProductView.__super__.constructor.apply(this, arguments);
    },

    /* ...snip... */
});
子视图变成:

var InsuranceProductView = ProductView.extend({
    consentTemplate: JST['application/templates/product/insurance_consent'],

    events:{
        'change input[type=radio]': 'showConsent',
        'change .insurance__accept': 'onInsuranceAccept'
    }

    initialize: function(options) {
        this.listenTo(this.model, 'change:selected', function(model) {
            if (!model.get('selected')) {
                this.removeMessage()
            }
        });
    },

    showConsent: function() {
        // I personally don't like when component go out of their root element.
        this.el.parentElement.appendChild(this.consentTemplate());
    },

    onInsuranceAccept: function() {
        InsuranceProductView.__super__.onChange.apply(this);
    },

    removeMessage: function() {
        var message = this.el.parentElement.querySelector('.insurance__consent');
        message.parentNode.removeChild(message);
    },
});
另外,主干
extend
在父级的原型中添加了
\uuuuuuuuuuuuuuuuuuuuuuuuuu
属性。我喜欢使用它,因为我可以更改父类,而不用担心在函数的某个地方使用它的原型


我发现,当使用较小的组件构建视图时,组合效果非常好

以下视图中几乎没有任何内容,除了较小组件的配置,每个组件都处理大部分复杂性:

var FoodMenu = Backbone.View.extend({
    template: '<div class="food-search"></div><div class="food-search-list"></div>',

    // abstracting selectors out of the view logic
    regions: {
        search: ".food-search",
        foodlist: ".food-search-list",
    },

    initialize: function() {

        // build your view with other components
        this.view = {
            search: new TextBox({
                label: 'Search foods',
                labelposition: 'top',
            }),
            foodlist: new FoodList({
                title: "Search results",
            })
        };
    },

    render: function() {
        this.$el.empty().append(this.template);

        // Caching scoped jquery element from 'regions' into `this.zone`.
        this.generateZones();
        var view = this.view,
            zone = this.zone;
        this.assign(view.search, zone.$search)
            .assign(view.foodlist, zone.$foodlist);

        return this;
    },

});
var foodmens=Backbone.View.extend({
模板:“”,
//从视图逻辑中抽象选择器
区域:{
搜索:“.食物搜索”,
食物列表:“.食物搜索列表”,
},
初始化:函数(){
//使用其他组件构建视图
此视图={
搜索:新文本框({
标签:“搜索食品”,
标签位置:'顶部',
}),
食物列表:新食物列表({
标题:“搜索结果”,
})
};
},
render:function(){
this.el.empty().append(this.template);
//将作用域为jquery的元素从“regions”缓存到“this.zone”。
这个。generateZones();
var view=this.view,
zone=这个区域;
this.assign(view.search,zone.$search)
.分配(view.foodlist,zone.$foodlist);
归还这个;
},
});
var FoodMenu = Backbone.View.extend({
    template: '<div class="food-search"></div><div class="food-search-list"></div>',

    // abstracting selectors out of the view logic
    regions: {
        search: ".food-search",
        foodlist: ".food-search-list",
    },

    initialize: function() {

        // build your view with other components
        this.view = {
            search: new TextBox({
                label: 'Search foods',
                labelposition: 'top',
            }),
            foodlist: new FoodList({
                title: "Search results",
            })
        };
    },

    render: function() {
        this.$el.empty().append(this.template);

        // Caching scoped jquery element from 'regions' into `this.zone`.
        this.generateZones();
        var view = this.view,
            zone = this.zone;
        this.assign(view.search, zone.$search)
            .assign(view.foodlist, zone.$foodlist);

        return this;
    },

});