2010-09-28 5 views
24

Ich habe mich schon lange mit der Idee beschäftigt, mein JavaScript objektorientierter zu machen. Ich habe mir auch ein paar verschiedene Implementierungen angeschaut, aber ich kann mich einfach nicht entscheiden, ob es notwendig ist oder nicht.Ist John Resigs OO-JavaScript-Implementierung sicher?

Was ich versuche zu beantworten folgende Fragen

  • Ist John Resig's einfache Vererbungsstruktur sicher für die Produktion zu nutzen?
  • Gibt es eine Möglichkeit zu sagen, wie gut es getestet wurde?
  • Neben Joose welche anderen Möglichkeiten habe ich für diesen Zweck? Ich brauche eine, die einfach zu bedienen, schnell und robust ist. Es muss auch mit jQuery kompatibel sein.
+22

Ich habe Javascript gesehen „in Produktion ", die Baby-Kätzchen-Engel zum Weinen bringt, also liegt es wirklich an Ihnen, zu entscheiden, welchen Einfluss es auf Ihre Unit-/Page-Tests haben wird. Es ist so einfach, dass man sich schwer vorstellen kann, dass es ernsthafte Probleme verursacht. – Pointy

+0

+1 Spitze. Ich habe gerade unten kritisiert, aber wirklich 99% der Websites haben mehr fragwürdigen Code als das auf ihnen bereits ... – bobince

+1

jQuery für einen Start ... –

Antwort

19

Huh. Es sieht viel komplizierter aus, als es für mich sein muss.

Eigentlich genauer schaue ich wirklich Ausnahme, was es tut, indem this._super() in einer Methode, die Superklasse-Methode aufrufen.

Der Code führt eine Abhängigkeit von typeof==='function' (unzuverlässig für einige Objekte), Function#toString (argh, Funktion Zersetzung ist auch unzuverlässig), und zu entscheiden, ob basierend wickeln, ob Sie die Reihenfolge des Bytes _super in dem Funktionskörper verwendet haben (Auch wenn Sie es nur in einer Zeichenfolge verwendet haben. Und wenn Sie versuchen, z. B. this['_'+'super'] wird es fehlschlagen).

Und wenn Sie Eigenschaften auf Ihren Funktionsobjekten speichern (z. B. MyClass.myFunction.SOME_PRIVATE_CONSTANT, was Sie tun könnten, um Namespaces sauber zu halten), werden Sie durch das Wrapping daran gehindert, diese Eigenschaften zu erreichen. Und wenn eine Ausnahme in einer Methode ausgelöst und in einer anderen Methode desselben Objekts gefangen wird, zeigt _super auf die falsche Sache.

All dies dient nur dazu, das Aufrufen der gleichnamigen Methode zu vereinfachen. Aber ich glaube nicht, dass das in JS sowieso besonders schwierig ist. Es ist zu schlau für sein eigenes Gut und macht das Ganze dadurch weniger zuverlässig. (Oh, und arguments.callee ist nicht gültig im strikten Modus, obwohl das nicht wirklich seine Schuld ist, da dies geschah, nachdem er es veröffentlicht hat.)

Hier ist, was ich für Klassen im Moment verwende. Ich behaupte nicht, dass dies das "beste" JS-Klassensystem ist, weil es lädt von verschiedenen Möglichkeiten, es zu tun und eine Reihe von verschiedenen Funktionen, die Sie hinzufügen oder nicht hinzufügen möchten. Aber es ist sehr leicht und zielt darauf ab, JavaScriptic zu sein, wenn das ein Wort ist. (Es ist nicht.)

Function.prototype.makeSubclass= function() { 
    function Class() { 
     if (!(this instanceof Class)) 
      throw 'Constructor function requires new operator'; 
     if ('_init' in this) 
      this._init.apply(this, arguments); 
    } 
    if (this!==Object) { 
     Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype; 
     Class.prototype= new Function.prototype.makeSubclass.nonconstructor(); 
    } 
    return Class; 
}; 
Function.prototype.makeSubclass.nonconstructor= function() {}; 

Es bietet:

  1. Schutz gegen versehentliches fehlt new. Die Alternative ist, X() zu new X() lautlos umzuleiten, so dass new funktioniert. Es ist ein Toss-Up, der am besten ist; Ich ging für explizite Fehler, so dass man sich nicht an das Schreiben ohne new gewöhnt und Probleme bei anderen Objekten verursacht, die nicht so definiert sind. So oder so ist es besser als der inakzeptable Standard von JS, this. Eigenschaften auf window fallen zu lassen und auf mysteriöse Weise später falsch zu gehen.

  2. eine vererbbare _init Methode, so dass Sie keine Konstruktor-Funktion schreiben müssen, die nur die Superklassenkonstruktorfunktion aufruft.

und das ist wirklich alles.

Hier ist, wie Sie es verwenden könnte Resig Beispiel zu implementieren:

var Person= Object.makeSubclass(); 
Person.prototype._init= function(isDancing) { 
    this.dancing= isDancing; 
}; 
Person.prototype.dance= function() { 
    return this.dancing; 
}; 

var Ninja = Person.makeSubclass(); 
Ninja.prototype._init= function() { 
    Person.prototype._init.call(this, false); 
}; 
Ninja.prototype.swingSword= function() { 
    return true; 
}; 

var p= new Person(true); 
p.dance(); // => true 

var n = new Ninja(); 
n.dance(); // => false 
n.swingSword(); // => true 

// Should all be true 
p instanceof Person && 
n instanceof Ninja && n instanceof Person 

Oberklasse-calling erfolgt durch speziell Benennung der Methode, die Sie wollen und call ing es, ein bisschen wie in Python. Sie könnte ein _super Mitglied der Konstruktorfunktion hinzufügen, wenn Sie wieder Person benennen wollten (so würden Sie sagen Ninja._super.prototype._init.call oder vielleicht Ninja._base._init.call).

+0

'typeof' ist nur unzuverlässig für Funktionen, wenn Sie mit Host-Objekten arbeiten, und Sie möchten keine davon ableiten. Oder zumindest sollten Sie nicht wollen. Ich stimme natürlich zu, dass Resigs Code übertrieben trickreich und zerbrechlich ist, und es gibt einfach keinen Grund dafür. –

+0

@Tim: Ja, Sie können nicht von einem Host-Objekt prototypieren, aber was der Funktionstest tut, überprüft jedes der Mitglieder, anstatt das Oberklassenobjekt. Ein Host-Objekt, das Mitglied Ihrer Klasse ist, kann daher Probleme verursachen. (zB in Firefox 3.0 kommt eine Regexp als Funktion heraus, so dass das Wrapping in Class.extend Mitglieder regexp ndern könnte, indem man versucht, sie wie Funktionen zu umbrechen). – bobince

+0

Oh, ich vergesse immer die Regexp-Sache. Und ja, du hast recht; Ich habe Herrn Resigs Code nicht so gründlich gelesen, wie ich es vielleicht getan hätte, und jetzt sieht es noch fragiler aus als zuvor. Gute Verwendung des Wortes "Nadger", übrigens. Es wird zu wenig genutzt. –

5

JavaScript ist prototypbasiert und nicht klassenbasiert. Meine Empfehlung ist nicht, es zu bekämpfen und Subtypes den JS Weg zu erklären:

MyDerivedObj.prototype = new MySuperObj(); 
MyDerivedObj.prototype.constructor = MyDerivedObj; 
+3

Das Problem dabei ist, dass der Aufruf von 'new MySuperObj()' den Initialisierungscode in der Konstruktorfunktion für 'MySuperObj()', als ob Sie tatsächlich ein 'MySuperObj' instanziieren würden. Sobald Sie etwas nicht-triviales in der Konstruktorfunktion haben, funktioniert dies nicht, da Sie den 'MySuperObj' -Initialisierungscode aufrufen müssen, wenn Sie eine' MyDerivedObj'-Instanz konstruieren, nicht zur Definitionszeit. – bobince

+1

Ich stimme zu, dass Sie vielleicht Ihren Initialisierungscode in eine separate Methode ausklammern und sie aus dem MyDerivedObj-Konstruktor aufrufen sollten. Ziehen Sie besser Dependency Injection in Betracht. Ich habe festgestellt, dass dies den Konstruktorcode stark einschränkt. –

5

Sehen Sie, wie weit Sie ohne Vererbung überhaupt erhalten können. Behandeln Sie es als Performance-Hack (nur widerwillig anzuwenden, wo es wirklich notwendig ist) und nicht als Designprinzip.

In einer hochdynamischen Sprache wie JS ist es selten notwendig zu wissen, ob ein Objekt einPerson ist. Sie müssen nur wissen, ob es einefirstName Eigenschaft oder eine eatFood Methode hat. Sie müssen nicht oft wissen, ob ein Objekt ein Array ist; Wenn es eine length-Eigenschaft und einige andere Eigenschaften hat, die nach Ganzzahlen benannt sind, ist das normalerweise gut genug (z. B. das Argument-Objekt). "Wenn es wie eine Ente geht und quakt wie eine Ente, ist es eine Ente."

// give back a duck 
return { 
    walk: function() { ... }, 
    quack: function() { ... } 
}; 

Ja, wenn Sie eine sehr große Anzahl von kleinen Objekten sind zu machen, die jeweils mit Dutzenden von Methoden, dann mit allen Mitteln, diese Methoden zu dem Prototyp zuweisen den Aufwand für die Erstellung Dutzende von Slots in jedem Fall zu vermeiden. Aber behandeln Sie das als eine Möglichkeit, den Speicheraufwand zu reduzieren - eine bloße Optimierung. Und tun Sie Ihren Benutzern einen Gefallen, indem Sie Ihre Verwendung von new hinter einer Art Fabrikfunktion verbergen, so dass sie nicht einmal wissen müssen, wie das Objekt erstellt wird. Sie müssen nur wissen, dass es Methode foo oder Eigenschaft bar hat.

(Und beachten Sie, dass Sie nicht wirklich wird in diesem Szenario klassische Erbe werden modellieren. Es ist nur das Äquivalent einer einzigen Klasse zu definieren, um die Effizienz eines gemeinsamen VTable zu erhalten.)