ChatGPT解决这个技术问题 Extra ChatGPT

Super in Backbone

When I override the clone() method of a Backbone.Model, is there a way to call this overriden method from my implantation? Something like this:

var MyModel = Backbone.Model.extend({
    clone: function(){
        super.clone();//calling the original clone method
    }
})

C
CreatordotJS

You'll want to use:

Backbone.Model.prototype.clone.call(this);

This will call the original clone() method from Backbone.Model with the context of this(The current model).

From Backbone docs:

Brief aside on super: JavaScript does not provide a simple way to call super — the function of the same name defined higher on the prototype chain. If you override a core function like set, or save, and you want to invoke the parent object's implementation, you'll have to explicitly call it.

var Note = Backbone.Model.extend({
 set: function(attributes, options) {
 Backbone.Model.prototype.set.apply(this, arguments);
 ...
 }    
});

Backbone docs seems to suggest e.g. Backbone.Model.prototype.set.apply(this, arguments); what is the difference between using prototype.func_name.apply(...) and prototype.func_name.call(...) ?
@MikaelLepistö see the question stackoverflow.com/questions/1986896/…
This wont work with the full prototype chain. If the direct super class' prototype doesn't contain the method, there'll be an exception.
c
charlysisto

You can also use the __super__ property which is a reference to the parent class prototype:

var MyModel = Backbone.Model.extend({
  clone: function(){
    MyModel.__super__.clone.call(this);
  }
});

Just a little background on this answer: __super__ is a reference to the parent's prototype that Backbone framework makes every time a Backbone model, collection, router, or view is extended. While it's not a standard property it does work cross-browser since its framework generated. However, even the Backbone official docs don't mention this and say to use Backbone.Model.prototype.set.call(this, attributes, options); method. Both seem to work fine, though.
@MauvisLedford Is your example code correct or should the .set. be .clone. for the OP's use case?
Mine was just a non-related example. It would be Backbone.Model.prototype.clone.call(this, attributes, options); in his case.
You could also use: this.constructor.__super__
@JasonM nope, this.constructor is not guaranteed to be MyModel and it would make a stack overflow if MyModel was used as a parent class.
D
David Sergey

Josh Nielsen found an elegant solution for this, which hides a lot of the ugliness.

Just add this snippet to your app to extend Backbone's model:

Backbone.Model.prototype._super = function(funcName){
    return this.constructor.prototype[funcName].apply(this, _.rest(arguments));
}

Then use it like this:

Model = Backbone.model.extend({
    set: function(arg){
        // your code here

        // call the super class function
        this._super('set', arg);
    }
});

this only works when there is only one level of implementation A.foo -> B.foo, in the case of A.foo -> B.foo -> C.foo you will get a stack overflow due to B's this refering to itself
My answer below fixes the problem of having more than one level of inheritance,
m
mab

Working from the answers given by geek_dave and charlysisto, I wrote this to add this._super(funcName, ...) support in classes that have multiple levels of inheritance. It's worked well in my code.

Backbone.View.prototype._super = Backbone.Model.prototype._super = function(funcName) {
        // Find the scope of the caller.
        var scope = null;
        var scan = this.__proto__;
        search: while (scope == null && scan != null) {
            var names = Object.getOwnPropertyNames(scan);
            for (var i = 0; i < names.length; i++) {
                if (scan[names[i]] === arguments.callee.caller) {
                    scope = scan;
                    break search;
                }
            }
            scan = scan.constructor.__super__;
        }
        return scan.constructor.__super__[funcName].apply(this, _.rest(arguments));
    };

A year later I've fixed some bugs and made things faster. Below is the code that I use now.

var superCache = {};

// Hack "super" functionality into backbone. 
Backbone.View.prototype._superFn = Backbone.Model.prototype._superFn = function(funcName, _caller) {
    var caller = _caller == null ? arguments.callee.caller : _caller;
    // Find the scope of the caller.
    var scope = null;
    var scan = this.__proto__;
    var className = scan.constructor.className;
    if (className != null) {
        var result = superCache[className + ":" + funcName];
        if (result != null) {
            for (var i = 0; i < result.length; i++) {
                if (result[i].caller === caller) {
                    return result[i].fn;
                }
            }
        }
    }
    search: while (scope == null && scan != null) {
        var names = Object.getOwnPropertyNames(scan);
        for (var i = 0; i < names.length; i++) {
            if (scan[names[i]] === caller) {
                scope = scan;
                break search;
            }
        }
        scan = scan.constructor.__super__;
    }
    var result = scan.constructor.__super__[funcName];
    if (className != null) {
        var entry = superCache[className + ":" + funcName];
        if (entry == null) {
            entry = [];
            superCache[className + ":" + funcName] = entry;
        }
        entry.push({
                caller: caller,
                fn: result
            });
    }
    return result;
};

Backbone.View.prototype._super = Backbone.Model.prototype._super = function(funcName) {
        var args = new Array(arguments.length - 1);
        for (var i = 0; i < args.length; i++) {
            args[i] = arguments[i + 1];
        }
        return this._superFn(funcName, arguments.callee.caller).apply(this, args);
    };

Then given this code:

var A = Backbone.Model.extend({ 
 //   className: "A",
    go1: function() { console.log("A1"); },  
    go2: function() { console.log("A2"); },  
    });

var B = A.extend({ 
 //   className: "B",
    go2: function() { this._super("go2"); console.log("B2"); },  
    });

var C = B.extend({ 
 //   className: "C",
    go1: function() { this._super("go1"); console.log("C1"); },
    go2: function() { this._super("go2"); console.log("C2"); }  
    });

var c = new C();
c.go1();
c.go2();

The output in the console is this:

A1
C1
A2
B2
C2

What's interesting is that class C's call to this._super("go1") scans the class hierarchy until it gets a hit in class A. Other solutions do not do this.

P.S. Uncomment the className entries of the class definitions to enable caching of the _super lookup. (The assumption is that these class names will be unique in the application.)


I wish I could upvote this answer more than once. First sensible description of super I've found on the internet. This concept always confused me, it's quite otherworldly from standard JavaScript...
R
Roman Krom

If you want just to call this._super(); without passing the function name as an argument

Backbone.Controller.prototype._super = function(){
    var fn = Backbone.Controller.prototype._super.caller, funcName;

    $.each(this, function (propName, prop) {
        if (prop == fn) {
            funcName = propName;
        }
    });

    return this.constructor.__super__[funcName].apply(this, _.rest(arguments));
}

Better use this plugin: https://github.com/lukasolson/Backbone-Super


s
swatkins

I believe you can cache the original method (although not tested):

var MyModel = Backbone.Model.extend({
  origclone: Backbone.Model.clone,
  clone: function(){
    origclone();//calling the original clone method
  }
});

It should be Backbone.Model.prototype.clone and this.origclone(). It's equivalent to Backbone.Model.prototype.clone.call(this).
K
Kabir Sarin

backbone._super.js, from my gists: https://gist.github.com/sarink/a3cf3f08c17691395edf

// Forked/modified from: https://gist.github.com/maxbrunsfeld/1542120
// This method gives you an easier way of calling super when you're using Backbone in plain javascript.
// It lets you avoid writing the constructor's name multiple times.
// You still have to specify the name of the method.
//
// So, instead of having to write:
//
//    var Animal = Backbone.Model.extend({
//        word: "",
//        say: function() {
//            return "I say " + this.word;
//        }
//    });
//    var Cow = Animal.extend({
//        word: "moo",
//        say: function() {
//            return Animal.prototype.say.apply(this, arguments) + "!!!"
//        }
//    });
//
//
// You get to write:
//
//    var Animal = Backbone.Model.extend({
//        word: "",
//        say: function() {
//            return "I say " + this.word;
//        }
//    });
//    var Cow = Animal.extend({
//        word: "moo",
//        say: function() {
//            return this._super("say", arguments) + "!!!"
//        }
//    });

(function(root, factory) {
    if (typeof define === "function" && define.amd) {
        define(["underscore", "backbone"], function(_, Backbone) {
            return factory(_, Backbone);
        });
    }
    else if (typeof exports !== "undefined") {
        var _ = require("underscore");
        var Backbone = require("backbone");
        module.exports = factory(_, Backbone);
    }
    else {
        factory(root._, root.Backbone);
    }
}(this, function(_, Backbone) {
    "use strict";

    // Finds the next object up the prototype chain that has a different implementation of the method.
    var findSuper = function(methodName, childObject) {
        var object = childObject;
        while (object[methodName] === childObject[methodName]) {
            object = object.constructor.__super__;
        }
        return object;
    };

    var _super = function(methodName) {
        // Keep track of how far up the prototype chain we have traversed, in order to handle nested calls to `_super`.
        this.__superCallObjects__ || (this.__superCallObjects__ = {});
        var currentObject = this.__superCallObjects__[methodName] || this;
        var parentObject  = findSuper(methodName, currentObject);
        this.__superCallObjects__[methodName] = parentObject;

        // If `methodName` is a function, call it with `this` as the context and `args` as the arguments, if it's an object, simply return it.
        var args = _.tail(arguments);
        var result = (_.isFunction(parentObject[methodName])) ? parentObject[methodName].apply(this, args) : parentObject[methodName];
        delete this.__superCallObjects__[methodName];
        return result;
    };

    // Mix in to Backbone classes
    _.each(["Model", "Collection", "View", "Router"], function(klass) {
        Backbone[klass].prototype._super = _super;
    });

    return Backbone;
}));

N
Nathan Hadzariga

In the case that you don't know what the parent class is exactly (multiple inheritance or you want a helper function) then you can use the following:

var ChildModel = ParentModel.extend({

  initialize: function() {
    this.__proto__.constructor.__super__.initialize.apply(this, arguments);
    // Do child model initialization.
  }

});

With helper function:

function parent(instance) {
  return instance.__proto__.constructor.__super__;
};

var ChildModel = ParentModel.extend({

  initialize: function() {
    parent(this).initialize.apply(this, arguments);
    // Do child model initialization.
  }

});

B
Blaine Kasten

Pass the parent class as an option during instantiation:

BaseModel = Backbone.Model.extend({
    initialize: function(attributes, options) {
        var self = this;
        this.myModel = new MyModel({parent: self});
    } 
});

Then in your MyModel you can call parent methods like this

this.options.parent.method(); Keep in mind this creates a retain cycle on the two objects. So to let the garbage collector do it's job you would need to manually destroy the retain on one of the objects when finished with it. If you're application is pretty large. I would encourage you to look more into hierarchal setups so events can travel up to the correct object.


That's not an inheritance example and not the same "parent" that is discussed in this thread. Also, the var self = this is unnecessary in this case since it's not inside a callback function losing the context.
Truth. I wrote this code when I was young and dumb. Thanks for the update here. Feel free to edit the post and update it. I don't write Backbone anymore, so I don't feel like I can confidently update it.
Editing it won't help since it's not answering the question at hand. I think the only option would be to remove the answer altogether.
A
Alan

2 functions below, one requires you pass in the function name, the other can "discover" which function we want the super version of

Discover.Model = Backbone.Model.extend({
       _super:function(func) {
        var proto = this.constructor.__super__;
        if (_.isUndefined(proto[func])) {
            throw "Invalid super method: " + func + " does not exist in prototype chain.";
        }
        return proto[func].apply(this, _.rest(arguments));
    },
    _superElegant:function() {
        t = arguments;
        var proto = this.constructor.__super__;
        var name;
        for (name in this) {
            if (this[name] === arguments.callee.caller) {
                console.log("FOUND IT " + name);
                break;
            } else {
                console.log("NOT IT " + name);
            }
        }
        if (_.isUndefined(proto[name])) {
            throw "Super method for: " + name + " does not exist.";
        } else {
            console.log("Super method for: " + name + " does exist!");
        }
        return proto[name].apply(this, arguments);
    },
});

A
Andy Stannard

Here is how I would do this:

ParentClassName.prototype.MethodToInvokeName.apply(this);

so for your example this is:

Model.prototype.clone.apply(this)