Why is my Backbone.View.constructor.__super__ unde

2019-05-01 11:34发布

问题:

I am subclassing my own Backbone.View. If, in the super class' initialize function, I write:

_.bindAll(this, 'many', 'methods');

And specify the methods that I want to bind to this context, I can call super from the subclass via:

this.constructor.__super__.initialize.apply(this, arguments);

But, if in the super class, I use:

_.bindAll(this)

instead, when I go to call super from my subclass,

this.constructor.__super__

is undefined. Any wisdom on why that is?

回答1:

Why not simply use this to call the super:

(I am separating to several lines for clarification, you can do the call in one line)

var thisProto = Object.getPrototypeOf(thisInstance);
var superProto = Object.getPrototypeOf(thisProto);
superProto.superMethod.apply(thisInstance, [param1, param2]);

Reference: GetPrototypeOf



回答2:

Seeing as there's only solutions to this problem but not explanations, I'm going to attempt to supply one...

When Underscore's bindAll method is invoked with the single argument (the object), all function type properties of that object no longer reference the original function but instead another that fixes the context.

Since one of the object's properties of type function is constructor, which is a reference to the Backbone constructor function (with the property __super__), the property will be overwritten with a new function. This then means that object.constructor will no longer have a property __super__.

To work around this issue I used the following function as an alternative to Underscore's bindAll:

function safeBindAll(obj) {
    var funcs = Array.prototype.slice.call(arguments, 1);

    if (funcs.length == 0) {
        funcs = _.functions(obj);
    }

    _.each(funcs, function(f) {
        var oldProps = obj[f];
        obj[f] = _.bind(obj[f], obj);

        _.extend(obj[f], oldProps);
    });

    return obj;
}

It's almost identical to Underscore's version but adds any properties of the original function to the new function through use of _.extend().



回答3:

I patched Backbone with getConstructor(), which returns the constructor and is immune to _.bindAll.

Usage:

this.getConstructor().__super__;

Implementation:

(function() {
  var backboneExtend = Backbone.Model.extend;
  var constructorExtend = function() {
    var child = backboneExtend.apply(this, arguments);
    child.prototype.getConstructor = function() {
      return child;
    };
    return child;
  };
  Backbone.Model.extend = Backbone.Collection.extend = Backbone.Router.extend = Backbone.View.extend = constructorExtend;
})();


回答4:

I'm new to Backbone but have over 4 years experience with another framework that also expects you to pass an object containing methods to an extend method, but the problem is this does not give sufficiently fine-grained control to act upon the methods contained within the object being passed.

To gain more control you can instead pass a closure to extend, and the closure can return an object. This technique in general allows finer-grained control and more potential for sophistication, and can be leveraged to solve your specific issue.

Inside the closure that returns the methods, those methods can be split into two broad categories:

  1. Backbone-specific, e.g. constructor & initialize
  2. Custom, specific to your application

If we return the custom methods specific to our application from their own separate object, we can then use _.bind for "partial application" of _.bindAll to just those custom method names.

Putting it all together then:

var foobar = Backbone.Model.extend(function() {
    var foobarMethods = modelMethods();

    return _.extend({}, foobarMethods, {
        constructor: function() {
            _.bind(_.bindAll, this, _.keys(foobarMethods));
            // equivalent to _.bindAll(this, 'foo', 'bar') except that the above
            // allow methods to be arbitrarily added or removed
        },
        initialize : function() {}          
    });

    //It's possible to "mixin" these methods from another file
    function modelMethods() {
        return {
            foo: function() {},
            bar: function() {},
        }
    }
}());