25 votes

Émuler super en javascript

Fondamentalement, est-il un bon mécanisme élégant pour émuler super avec la syntaxe est aussi simple que l'une des opérations suivantes

  • this.$super.prop()
  • this.$super.prop.apply(this, arguments);

Les critères à respecter sont :

  1. this.$super doit être une référence pour le prototype. c'est à dire si je change le super prototype au moment de l'exécution de ce changement pourra être répercuté. Ceci signifie fondamentalement, il le parent a une nouvelle propriété, alors cela devrait être indiqué au moment de l'exécution sur tous les enfants par le biais super tout comme une codés en dur de référence à la société mère de refléter les changements
  2. this.$super.f.apply(this, arguments); doivent travailler pour les appels récursifs. Pour toute enchaînés ensemble de l'héritage où plusieurs super les appels sont faits que vous allez en haut de la chaîne d'héritage, vous ne devez pas frapper le récursive problème.
  3. Vous ne devez pas coder en dur des références à des super objets de vos enfants. I. e. Base.prototype.f.apply(this, arguments); de défaites le point.
  4. Vous ne devez pas utiliser un X à compilateur JavaScript ou des scripts JavaScript de préprocesseur.
  5. Doit être conforme ES5

L'implémentation naïve serait quelque chose comme ceci.

var injectSuper = function (parent, child) {
  child.prototype.$super = parent.prototype;
};

Mais cela rompt la condition 2.

Le plus élégant mécanisme que j'ai vu à ce jour est IvoWetzel de l' eval hack, qui est à peu près un JavaScript préprocesseur et échoue donc les critères 4.

10voto

hugomg Points 29789

Je ne pense pas qu'il y est "libre" moyen de sortir de la "récursive super" problème que vous mentionnez.

Nous ne pouvons pas toucher à l' this , car cela aurait pour effet soit de nous forcer à changer de prototypes dans un autre chemin, ou nous déplacer jusqu'le proto de la chaîne, de perdre des variables d'instance. Par conséquent, l'actuel "classe" et "super-classe" doit être connue lorsque nous ne le super-ing, sans passage que la responsabilité d' this ou de l'une de ses propriétés.

Il y a beaucoup de certaines choses qu'on pourrait essayer de faire mais tout ce que je pense avoir une certaine undesireable conséquences:

  • Ajouter super info pour les fonctions au moment de la création, de l'accès à l'aide d'arguments.calee ou similaire méchanceté.
  • Ajouter des informations supplémentaires lors de l'appel de la méthode super

    $super(CurrentClass).method.call(this, 1,2,3)
    

    Cela nous oblige à dupliquer l'actuel nom de la classe (si nous pouvons trouver sa super-classe dans un super dictionnaire), mais au moins il n'est pas aussi mauvais que d'avoir à dupliquer le nom de la superclasse, (depuis couplage contre les relations d'héritage si pire que de l'intérieur de couplage avec une classe propre nom)

    //Normal Javascript needs the superclass name
    SuperClass.prototype.method.call(this, 1,2,3);
    

    Alors que c'est loin d'être idéale, il y a au moins certains précédents historiques à partir de 2.x de Python. (Ils "fixe" pour le super 3.0 de sorte qu'il ne nécessite pas d'arguments plus, mais je ne suis pas sûr de la quantité de magie que les impliqués et comment portable il serait JS)


Edit: Travail de violon

var superPairs = [];
// An association list of baseClass -> parentClass

var injectSuper = function (parent, child) {
    superPairs.push({
        parent: parent,
        child: child
    });
};

function $super(baseClass, obj){
    for(var i=0; i < superPairs.length; i++){
        var p = superPairs[i];
        if(p.child === baseClass){
            return p.parent;
        }
    }
}

5voto

ngryman Points 611

John Resig posté un ineherence mécanisme simple mais génial superde soutien. La seule différence est qu' super de points à la méthode de base de l'endroit où vous êtes en l'appelant.

Jetez un oeil à http://ejohn.org/blog/simple-javascript-inheritance/.

2voto

Thomas Eding Points 8651

A noter que pour la suite de la mise en œuvre, lorsque vous êtes à l'intérieur d'une méthode qui est appelée par $super, l'accès aux propriétés tout en travaillant dans la classe parent ne jamais résoudre pour les enfants de la classe de méthodes ou variables, sauf si vous accédez à un membre qui est stocké directement sur l'objet lui-même (par opposition à joint pour le prototype). Cela évite un tas de confusion (lire aussi subtile bugs).

Mise à jour: Voici une application qui fonctionne sans __proto__. Le hic, c'est que l'utilisation d' $super est linéaire en le nombre de propriétés de l'objet parent est.

function extend (Child, prototype, /*optional*/Parent) {
    if (!Parent) {
        Parent = Object;
    }
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    for (var x in prototype) {
        if (prototype.hasOwnProperty(x)) {
            Child.prototype[x] = prototype[x];
        }
    }
    Child.prototype.$super = function (propName) {
        var prop = Parent.prototype[propName];
        if (typeof prop !== "function") {
            return prop;
        }
        var self = this;
        return function () {
            var selfPrototype = self.constructor.prototype;
            var pp = Parent.prototype;
            for (var x in pp) {
                self[x] = pp[x];
            }
            try {
                return prop.apply(self, arguments);
            }
            finally {
                for (var x in selfPrototype) {
                    self[x] = selfPrototype[x];
                }
            }
        };
    };
}

La mise est pour les navigateurs qui prennent en charge l' __proto__ de la propriété:

function extend (Child, prototype, /*optional*/Parent) {
    if (!Parent) {
        Parent = Object;
    }
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    for (var x in prototype) {
        if (prototype.hasOwnProperty(x)) {
            Child.prototype[x] = prototype[x];
        }
    }
    Child.prototype.$super = function (propName) {
        var prop = Parent.prototype[propName];
        if (typeof prop !== "function") {
            return prop;
        }
        var self = this;
        return function (/*arg1, arg2, ...*/) {
            var selfProto = self.__proto__;
            self.__proto__ = Parent.prototype;
            try {
                return prop.apply(self, arguments);
            }
            finally {
                self.__proto__ = selfProto;
            }
        };
    };
}

Exemple:

function A () {}
extend(A, {
    foo: function () {
        return "A1";
    }
});

function B () {}
extend(B, {
    foo: function () {
        return this.$super("foo")() + "_B1";
    }
}, A);

function C () {}
extend(C, {
    foo: function () {
        return this.$super("foo")() + "_C1";
    }
}, B);


var c = new C();
var res1 = c.foo();
B.prototype.foo = function () {
    return this.$super("foo")() + "_B2";
};
var res2 = c.foo();

alert(res1 + "\n" + res2);

2voto

Axel Rauschmayer Points 2401

La principale difficulté de la super , c'est que vous avez besoin de trouver ce que j'appelle here: l'objet qui contient la méthode qui fait de la super référence. Qui est absolument nécessaire pour obtenir le droit de la sémantique. Évidemment, en ayant le prototype de l' here est tout aussi bon, mais cela ne fait pas beaucoup de différence. La suite est une solution statique:

// Simulated static super references (as proposed by Allen Wirfs-Brock)
// http://wiki.ecmascript.org/doku.php?id=harmony:object_initialiser_super

//------------------ Library

function addSuperReferencesTo(obj) {
    Object.getOwnPropertyNames(obj).forEach(function(key) {
        var value = obj[key];
        if (typeof value === "function" && value.name === "me") {
            value.super = Object.getPrototypeOf(obj);
        }
    });
}

function copyOwnFrom(target, source) {
    Object.getOwnPropertyNames(source).forEach(function(propName) {
        Object.defineProperty(target, propName,
            Object.getOwnPropertyDescriptor(source, propName));
    });
    return target;
};

function extends(subC, superC) {
    var subProto = Object.create(superC.prototype);
    // At the very least, we keep the "constructor" property
    // At most, we preserve additions that have already been made
    copyOwnFrom(subProto, subC.prototype);
    addSuperReferencesTo(subProto);
    subC.prototype = subProto;
};

//------------------ Example

function A(name) {
    this.name = name;
}
A.prototype.method = function () {
    return "A:"+this.name;
}

function B(name) {
    A.call(this, name);
}
// A named function expression allows a function to refer to itself
B.prototype.method = function me() {
    return "B"+me.super.method.call(this);
}
extends(B, A);

var b = new B("hello");
console.log(b.method()); // BA:hello

1voto

Bill Barry Points 804

JsFiddle:

Ce qui est mal à cela?

'use strict';

function Class() {}
Class.extend = function (constructor, definition) {
    var key, hasOwn = {}.hasOwnProperty, proto = this.prototype, temp, Extended;

    if (typeof constructor !== 'function') {
        temp = constructor;
        constructor = definition || function () {};
        definition = temp;
    }
    definition = definition || {};

    Extended = constructor;
    Extended.prototype = new this();

    for (key in definition) {
        if (hasOwn.call(definition, key)) {
            Extended.prototype[key] = definition[key];
        }
    }

    Extended.prototype.constructor = Extended;

    for (key in this) {
        if (hasOwn.call(this, key)) {
            Extended[key] = this[key];
        }
    }

    Extended.$super = proto;
    return Extended;
};

Utilisation:

var A = Class.extend(function A () {}, {
    foo: function (n) { return n;}
});
var B = A.extend(function B () {}, {
    foo: function (n) {
        if (n > 100) return -1;
        return B.$super.foo.call(this, n+1);
    }
});
var C = B.extend(function C () {}, {
    foo: function (n) {
        return C.$super.foo.call(this, n+2);
    }
});

var c = new C();
document.write(c.foo(0) + '<br>'); //3
A.prototype.foo = function(n) { return -n; };
document.write(c.foo(0)); //-3

Exemple d'utilisation privilégiée avec les méthodes à la place des méthodes publiques.

var A2 = Class.extend(function A2 () {
    this.foo = function (n) {
        return n;
    };
});
var B2 = A2.extend(function B2 () {
    B2.$super.constructor();
    this.foo = function (n) {
        if (n > 100) return -1;
        return B2.$super.foo.call(this, n+1);
    };
});
var C2 = B2.extend(function C2 () {
    C2.$super.constructor();
    this.foo = function (n) {
        return C2.$super.foo.call(this, n+2);
    };
});

//you must remember to constructor chain
//if you don't then C2.$super.foo === A2.prototype.foo

var c = new C2();
document.write(c.foo(0) + '<br>'); //3

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X