Einfach "Klasse" Instanziierung
//makeClass - By John Resig (MIT Licensed)
function makeClass(){
return function(args){
if ( this instanceof arguments.callee ) {
if ( typeof this.init == "function" )
this.init.apply( this, args.callee ? args : arguments );
} else
return new arguments.callee( arguments );
};
}
besonders diese Zeile this.init.apply( this, args.callee ? args : arguments );
Was ist der Unterschied zwischen args
und arguments
? Kann args.callee
je false
?
- classic, // makeClass - Von John Resig (MIT Lizenz)
Du musst angemeldet sein, um einen Kommentar abzugeben.
Schreiben Sie, dass die vorhandenen Antworten nicht detailliert genug, aber auch nach der Lektüre Ihre konkreten Fragen, ich bin mir nicht ganz sicher, genau welche Teile des Codes sind, werfen Sie für eine Schleife — es hat eine Reihe von kniffligen teilen — so dass ich entschuldige mich im Voraus, wenn diese Antwort geht über Bord mit details über Dinge, die Sie haben schon verstanden!
Seit
makeClass
ist immer dazu gedacht, genannt zu werden die gleiche Weise, es ist ein bisschen leichter zu Grunde geht, wenn wir entfernen Sie eine Ebene der Dereferenzierung. Diese:entspricht dies:
Da wir nicht mehr um eine anonyme Funktion, die wir nicht mehr brauchen, die
arguments.callee
notation: es notwendigerweise bezieht sich aufMyClass
, so können wir ersetzen Sie alle Instanzen der es mitMyClass
verleiht dieser:wo
args
ist ein Bezeichner fürMyClass
's ersten argument, undarguments
wie immer, ist ein array-artiges Objekt mit alle vonMyClass
's Argumente.Die Zeile, die Sie sind, Fragen, ist nur dann erreicht, wenn die "Klasse" hat eine Funktion namens
init
in der Prototypen (die "constructor"), also geben wir es ein:Sobald wir das getan haben, betrachten Sie dieses:
Innerhalb der Aufruf
MyClass
,this
beziehen sich auf das Objekt konstruiert, sothis instanceof MyClass
wahr werden. Undtypeof this.init == "function"
wahr werden, weil wirMyClass.prototype.init
eine Funktion sein. So erreichen wir diese Zeile:Hier
args
gleich'value'
(das erste argument), es ist also eine Zeichenfolge, sodass es nicht diecallee
Eigenschaft; soargs.callee
ist nicht definiert, die in einem booleschen Kontext bedeutet es ist falsch, soargs.callee ? args : arguments
entsprichtarguments
. Daher die obige Zeile ist äquivalent zu dieser:entspricht dies:
(wenn Sie nicht bereits wissen, wie
apply
Werke, und wie unterscheidet es sich voncall
finden Sie https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/apply).Macht das Sinn so weit?
Anderen Fall zu prüfen, ist dies:
Innerhalb der Aufruf
MyClass
,this
beziehen sich auf das Globale Objekt (in der Regelwindow
), sothis instanceof MyClass
wird falsch sein, so erreichen wir diese Zeile:wo
arguments
ist ein array-artiges Objekt mit einem einzigen element:'value'
. Beachten Sie, dass dies nicht das gleiche wienew MyClass('value')
.Terminologische Anmerkung: So der Aufruf
MyClass('value')
Ergebnisse in einem zweiten AufrufMyClass
, dieses mal mitnew
. Ich nenne den ersten Anruf (ohnenew
) die "äußeren-Aufruf", und den zweiten Aufruf (mitnew
) die "inneren Ruf". Hoffe, dass das intuitive.Innerhalb des inneren Ruf zu
MyClass
,args
bezieht sich jetzt auf die äußeren-Aufrufarguments
Objekt: stattargs
wird'value'
haben, ist es nun ein array-artiges Objekt mit'value'
. Und stattargs.callee
wird nicht definiert, es bezieht sich jetzt aufMyClass
, soargs.callee ? args : arguments
entsprichtargs
. Also die inneren Ruf zuMyClass
ruftthis.init.apply(this, args)
entsprichtthis.init('value')
.Also der test auf
args.callee
soll unterschieden werden zwischen einer inneren Ruf (MyClass('value')
→new MyClass(arguments)
) aus einer normal direkten Anruf (new MyClass('value')
). Im Idealfall könnten wir ausschließen, dass der test durch ersetzen Sie diese Zeile:etwas hypothetisches, die wie folgt aussah:
aber JavaScript nicht erlauben, dass die notation (oder äquivalente Schreibweise).
Können Sie sehen, durch die Art und Weise, dass es ein paar kleine Probleme mit Resig code:
MyClass.prototype.init
, und dann instanzieren wir die "Klasse" durch das schreibenvar myInstance3 = new MyClass();
, dannargs
undefiniert im inneren der AufrufMyClass
, so dass der test aufargs.callee
wird ein Fehler ausgelöst. Ich denke, dass das ist einfach ein bug auf Resig Teil; jedenfalls, es ist leicht behoben werden durch testen aufargs && args.callee
statt.callee
, dann ist der test aufargs.callee
wird zu einem false positive und die falschen Argumente übergeben werden, in den Konstruktor. Dies bedeutet, dass, zum Beispiel, wir können nicht design-Konstruktor, um einearguments
- Objekt als erstes argument. Aber dieses Problem scheint schwierig zu umgehen, und es lohnt sich wahrscheinlich nicht sorgen zu machen.new MyClass.apply( itself, arguments )
, aber JavaScript nicht erlauben, dass die notation (oder äquivalente Schreibweise)" – das ist der Punkt.Object.create
(verpackt oder nicht), soinit
Methode muss zu einer üblichen Praxis. Im Jahr 2007 könnte es scheinen eine gute option, um immer noch die Verwendung von Konstruktoren und ausblendeninit
entfernt. Ich würde nur eine exception werfen bei einem Aufruf ohnenew
machen und die user rufeninit
explizit nach dem erstellen einer Instanz.@ruakh: Tolle Analyse. Fast zwei Jahre nach der ursprünglichen Frage und deine Antwort, ich bin noch angezogen, um die Angelegenheit. Ich hoffe, dass meine Beobachtungen nicht ganz überflüssig. Sie sind Recht lang, obwohl. Rechtfertigen würde einen schönen stand-alone-blog-Artikel :-).
Beide Fragen mit John Resig ' s original-code, den Sie erwähnen am Ende, können gelöst werden using a private flag zu unterscheiden, was Sie rufen einen innere von einem äußeren nennen.
Können wir auch loswerden mit
arguments.callee
insgesamt durch die Zuweisung des anonymen Funktion eine lokale variable vor der Rückkehr es.Es ist sogar möglich, zu vermeiden, dass das innere Aufruf an alle, wie diese, die ist auch sehr gut für die Leistung. Wenn wir einen modernen JavaScript, das hat
Object.create
können wir vereinfachen wie folgt:Dies ist nicht die Schnellste Lösung, obwohl. Können wir vermeiden, dass die prototype-chain-lookup-beginnend bei der Instanz-Objekt, weil wir wissen, dass
init
muss in der Prototyp.So dürfen wir einen
var init=constructor.prototype.init
- Anweisung, um es zu erhalten, dann überprüfen Sie es fürfunction
geben, und dann gilt es.Wenn wir wollen, werden rückwärts-kompatibel ist, wir können entweder laden Sie eine der vorhandenen polyfills, e. g. von Mozilla Developer Network, oder benutzen Sie den folgenden Ansatz:
Wenn Sie sich entscheiden, gegen die Verwendung von 'public static final'
Function.VOID
so,Sie können eine Erklärung wie
var VOID=function(){}
obenmakeClass
. Aber das wird zu einer eigenen Funktion erstellt werden, innerhalb jeder Konstruktor der Klasse, die Sie gehen zu produzieren.Wir können auch definieren eine "statische" Methode für das Dienstprogramm selbst mit
makeClass.VOID=function(){}
.Ein weiteres beliebtes Muster ist das bestehen einer single Instanz dieser kleinen Funktion in
makeClass
mit einem sofort sogenannte wrapper-Funktion.Suchen auf diesem code, den wir vielleicht ganz verwirrt. Jeder Instanz jeder Konstruktor der Klasse, die wir erstellen werden in der Zukunft mit der direkten Konstruktor-Aufruf ohne
new
wird technisch eine Instanz von ein und dem selben void Konstruktor, nämlich denfunction(){}
wir übergeben als argument für unsere wrapper-Funktion.Wie kann das überhaupt funktionieren?
Verzeihen Sie mir, wenn ich werde erklären, was Sie bereits wissen.
Das Geheimnis liegt in der Tatsache, dass wir ändern den Prototyp
Void
zuconstructor.prototype
vor wir verwendennew
zu instanziieren. An diesem Punkt, jedes neue Objekt bekommt eine interne Eigenschaft informell gekennzeichnet durch[[Prototype]]
dessen Wert ist der aktuelle Wert der constructor prototype-Eigenschaft. Wenn der Wert der constructor-die prototype-Eigenschaft wird später ausgetauscht, es hat keine weitere Auswirkung auf unser Objekt gerade erstellt haben.Siehe: Abschnitt 13.2.2 [[Bauen]] der ECMA-Standard 262 5th Edition.
Daher die folgenden Werke für alle "Klassen", die wir mit diesem tool:
Args ist ein parameter der Funktion selbst.
Absolut,
Also im Beispiel oben:
gibt diese folgende Funktion gespeichert in der Variablen
class
so, wenn ich rufe
class({args: false});
so
args
gibt Ihnen die Möglichkeit zum überschreiben der Standard -arguments
erstellt von javascriptmakeClass()
funktioniert und wie es eingesetzt werden soll.window.onload
Sie erklären würde, dasswindow
können lokale Variablen sein undonload
kann eine ganze Zahl geschätzte Eigenschaft, dass eine variable?Ich glaube, an dieser Stelle diese Funktion umgeschrieben werden kann, um zu appellieren, um den strikten Modus in ES5 und darüber hinaus.
arguments.callee
geben Sie Problemen, wenn Sie haben irgendeine Art von linter Blick auf Ihren code. Ich glaube, der code kann wie folgt umgeschrieben werden(http://jsfiddle.net/skipallmighty/bza8qwmw/):Könnten Sie erstellen, die Vererbung wie folgt:
Wenn ich bin falsch über die re-Implementierung dieser Klasse dann würde ich mich sehr freuen, zu wissen, wie kann ich verbessern.
Folgende auf Ihrem Titel die Frage eher als die spezifische Frage zu Ihrem Beispiel:
Ich nie wirklich, warum Sie benötigen, es zu erschweren, wie dies. Warum nicht genau dies tun? Es ist ein besseres Beispiel (nach mir) der "einfachen" Klasse Instanziierung in js: