Super im Rückgrat

74

Gibt es eine Möglichkeit, diese Überschreibungsmethode von meiner Implantation aus aufzurufen, wenn ich die clone()Methode von a Backbone.Modelüberschreibe? Etwas wie das:

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

Antworten:

100

Sie möchten verwenden:

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

Dadurch wird die ursprüngliche clone()Methode Backbone.Modelmit dem Kontext von this(Das aktuelle Modell) aufgerufen .

Aus Backbone-Dokumenten :

Kurz beiseite zu Super: JavaScript bietet keine einfache Möglichkeit, Super aufzurufen - die gleichnamige Funktion, die weiter oben in der Prototypenkette definiert ist. Wenn Sie eine Kernfunktion wie set oder save überschreiben und die Implementierung des übergeordneten Objekts aufrufen möchten, müssen Sie diese explizit aufrufen.

var Note = Backbone.Model.extend({
 set: function(attributes, options) {
 Backbone.Model.prototype.set.apply(this, arguments);
 ...
 }    
});
soldat.motte
quelle
Backbone-Dokumente scheinen z. B. Backbone.Model.prototype.set.apply (dies, Argumente) vorzuschlagen; Was ist der Unterschied zwischen der Verwendung von prototype.func_name.apply (...) und prototype.func_name.call (...)?
Mikael Lepistö
5
@ MikaelLepistö siehe die Frage stackoverflow.com/questions/1986896/…
soldat.moth
1
Dies funktioniert nicht mit der gesamten Prototypenkette. Wenn der Prototyp der direkten Superklasse die Methode nicht enthält, gibt es eine Ausnahme.
Tobias Cudnik
36

Sie können auch die __super__Eigenschaft verwenden, die auf den Prototyp der übergeordneten Klasse verweist:

var MyModel = Backbone.Model.extend({
  clone: function(){
    MyModel.__super__.clone.call(this);
  }
});
charlysisto
quelle
22
Nur ein kleiner Hintergrund zu dieser Antwort: Dies __super__ist ein Verweis auf den Prototyp des übergeordneten Elements, den das Backbone-Framework jedes Mal erstellt, wenn ein Backbone-Modell, eine Sammlung, ein Router oder eine Ansicht erweitert wird. Obwohl es sich nicht um eine Standardeigenschaft handelt, funktioniert es seit der Generierung des Frameworks browserübergreifend. Selbst die offiziellen Backbone-Dokumente erwähnen dies jedoch nicht und sagen, dass sie die Backbone.Model.prototype.set.call(this, attributes, options);Methode verwenden sollen. Beide scheinen jedoch gut zu funktionieren.
Mauvis Ledford
@MauvisLedford Ist Ihr Beispielcode korrekt oder sollte .set.er .clone.für den Anwendungsfall des OP sein?
MikeSchinkel
Meins war nur ein nicht verwandtes Beispiel. Es wäre Backbone.Model.prototype.clone.call(this, attributes, options);in seinem Fall.
Mauvis Ledford
2
Sie könnten auch verwenden: this.constructor .__ super__
Jason M
@JasonM nein, dies this.constructorist nicht garantiert MyModelund würde einen Stapelüberlauf verursachen, wenn MyModeler als übergeordnete Klasse verwendet würde.
Emile Bergeron
18

Josh Nielsen hat dafür eine elegante Lösung gefunden , die einen Großteil der Hässlichkeit verbirgt.

Fügen Sie einfach diesen Ausschnitt zu Ihrer App hinzu, um das Backbone-Modell zu erweitern:

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

Dann benutze es so:

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

        // call the super class function
        this._super('set', arg);
    }
});
Dave Cadwallader
quelle
5
Dies funktioniert nur, wenn es nur eine Implementierungsebene gibt. A.foo -> B.foo, im Fall von A.foo -> B.foo -> C.foo erhalten Sie einen Stapelüberlauf, da sich B auf sich selbst bezieht
-Panic
Meine Antwort unten behebt das Problem, mehr als eine Vererbungsstufe zu haben,
mab
4

Ausgehend von den Antworten von geek_dave und charlysisto habe ich dies geschrieben, um this._super(funcName, ...)Unterstützung für Klassen mit mehreren Vererbungsebenen hinzuzufügen . In meinem Code hat es gut funktioniert.

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));
    };

Ein Jahr später habe ich einige Fehler behoben und die Dinge schneller gemacht. Unten ist der Code, den ich jetzt benutze.

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);
    };

Dann geben Sie diesen 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();

Die Ausgabe in der Konsole lautet wie folgt:

A1
C1
A2
B2
C2

Interessant ist, dass die Klasse C this._super("go1")die Klassenhierarchie scannt, bis sie in Klasse A getroffen wird. Andere Lösungen tun dies nicht.

PS Kommentieren Sie die classNameEinträge der Klassendefinitionen aus, um das Zwischenspeichern der _superSuche zu ermöglichen . (Es wird davon ausgegangen, dass diese Klassennamen in der Anwendung eindeutig sind.)

mab
quelle
Ich wünschte, ich könnte diese Antwort mehr als einmal positiv bewerten. Erste sinnvolle Beschreibung von Super habe ich im Internet gefunden. Dieses Konzept hat mich immer verwirrt, es ist ziemlich jenseitig von Standard-JavaScript ...
I_ATE_YOUR_WORK_FILES
3

Wenn Sie nur this._super () aufrufen möchten; ohne den Funktionsnamen als Argument zu übergeben

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));
}

Verwenden Sie dieses Plugin besser: https://github.com/lukasolson/Backbone-Super

Roman Krom
quelle
2

Ich glaube, Sie können die ursprüngliche Methode zwischenspeichern (obwohl nicht getestet):

var MyModel = Backbone.Model.extend({
  origclone: Backbone.Model.clone,
  clone: function(){
    origclone();//calling the original clone method
  }
});
Swatkins
quelle
Es sollte Backbone.Model.prototype.cloneund sein this.origclone(). Es ist gleichbedeutend mit Backbone.Model.prototype.clone.call(this).
Emile Bergeron
1

backbone._super.js, aus meinem Kern: 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;
}));
Sarink
quelle
1

Falls Sie nicht genau wissen, was die übergeordnete Klasse ist (Mehrfachvererbung oder Sie möchten eine Hilfsfunktion), können Sie Folgendes verwenden:

var ChildModel = ParentModel.extend({

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

});

Mit Hilfsfunktion:

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

var ChildModel = ParentModel.extend({

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

});
Nathan Hadzariga
quelle
0

Übergeben Sie die übergeordnete Klasse als Option während der Instanziierung:

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

Dann können Sie in Ihrem MyModel übergeordnete Methoden wie diese aufrufen

this.options.parent.method (); Beachten Sie, dass dadurch ein Aufbewahrungszyklus für die beiden Objekte erstellt wird. Damit der Garbage Collector seine Arbeit erledigen kann, müssen Sie die Aufbewahrung eines der Objekte manuell zerstören, wenn Sie damit fertig sind. Wenn Ihre Anwendung ziemlich groß ist. Ich möchte Sie ermutigen, sich eingehender mit hierarchischen Einstellungen zu befassen, damit Ereignisse zum richtigen Objekt gelangen können.

Blaine Kasten
quelle
Dies ist kein Vererbungsbeispiel und nicht dasselbe "übergeordnete Element", das in diesem Thread behandelt wird. Auch das var self = thisist nicht notwendig , in diesem Fall , da es nicht in einer Callback - Funktion ist zu verlieren , den Kontext.
Emile Bergeron
1
Wahrheit. Ich habe diesen Code geschrieben, als ich jung und dumm war. Danke für das Update hier. Fühlen Sie sich frei, den Beitrag zu bearbeiten und zu aktualisieren. Ich schreibe kein Backbone mehr, daher habe ich nicht das Gefühl, dass ich es sicher aktualisieren kann.
Blaine Kasten
Das Bearbeiten hilft nicht, da die vorliegende Frage nicht beantwortet wird. Ich denke, die einzige Möglichkeit wäre, die Antwort insgesamt zu entfernen.
Emile Bergeron
0

2 Funktionen unten, eine erfordert die Übergabe des Funktionsnamens, die andere kann "herausfinden", von welcher Funktion wir die Super-Version wollen

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);
    },
});
Alan
quelle
-1

So würde ich das machen:

ParentClassName.prototype.MethodToInvokeName.apply(this);

Für Ihr Beispiel lautet dies also:

Model.prototype.clone.apply(this)
Andy Stannard
quelle