Ein Objekt ist eine Kombination aus Eigenschaften und Methoden.
Verwandte Themen:
Mit IsObject kann festgestellt werden, ob ein Wert ein Objekt ist:
Ergebnis := IsObject(Ausdruck)
Eine Liste von Standardobjekttypen finden Sie unter Interne Klassen. Es gibt zwei Grundtypen:
Erstellen eines Arrays:
MeinArray := [Element1, Element2, ..., ElementN] MeinArray := Array(Element1, Element2, ..., ElementN)
Abrufen eines Elements:
Wert := MeinArray[Index]
Ändern eines Elementwertes (Index
muss zwischen 1 und Länge liegen oder ein äquivalenter umgekehrter Index sein):
MeinArray[Index] := Wert
Einfügen eines oder mehrerer Elemente ab einem bestimmten Index via InsertAt-Methode:
MeinArray.InsertAt(Index, Wert, Wert2, ...)
Anfügen eines oder mehrerer Elemente via Push-Methode:
MeinArray.Push(Wert, Wert2, ...)
Entfernen eines Elements via RemoveAt-Methode:
EntfernterWert := MeinArray.RemoveAt(Index)
Entfernen eines Elements via Pop-Methode:
EntfernterWert := MeinArray.Pop()
Length gibt die Anzahl der Elemente im Array zurück. Um die Elemente eines Arrays einzeln durchzugehen, verwenden Sie entweder A_Index oder eine For-Schleife. Zum Beispiel:
MeinArray := ["eins", "zwei", "drei"] ; Von 1 bis zum Ende des Arrays iterieren: Loop MeinArray.Length MsgBox MeinArray[A_Index] ; Den Inhalt des Arrays enumerieren: For Index, Wert in MeinArray MsgBox "Element " Index " ist '" Wert "'" ; Dasselbe noch einmal: For Wert in MeinArray MsgBox "Element " A_Index " ist '" Wert "'"
Ein Map oder assoziatives Array ist ein Objekt, das eine Sammlung von eindeutigen Schlüsseln und eine Sammlung von Werten enthält, wobei jeder Schlüssel mit einem Wert verbunden ist. Schlüssel können Zeichenketten, Integer oder Objekte sein, während Werte von beliebigem Typ sein können. Ein assoziatives Array kann wie folgt erstellt werden:
MeinMap := Map("SchlüsselA", WertA, "SchlüsselB", WertB, ..., "SchlüsselZ", WertZ)
Abrufen eines Elements, wobei Schlüssel
eine Variable oder ein Ausdruck ist:
Wert := MeinMap[Schlüssel]
Zuweisen eines Elements:
MeinMap[Schlüssel] := Wert
Entfernen eines Elements via Delete-Methode:
EntfernterWert := MeinMap.Delete(Schlüssel)
Enumerieren von Elementen:
MeinMap := Map("zehn", 10, "zwanzig", 20, "dreißig", 30) For Schlüssel, Wert in MeinMap MsgBox Schlüssel ' = ' Wert
Ein Objekt kann Eigenschaften und Elemente (z.B. Array-Elemente) haben. Der Zugriff auf Elemente erfolgt über []
, wie in den vorherigen Abschnitten gezeigt. Der Zugriff auf Eigenschaften erfolgt durch Anfügen eines Punktes und einem Identifikator (einfach ein Name). Methoden sind Eigenschaften, die aufgerufen werden können.
Beispiele:
Abrufen oder Setzen einer Eigenschaft mit dem direkt geschriebenen Namen Eigenschaft:
Wert := MeinObjekt.Eigenschaft
MeinObjekt.Eigenschaft := Wert
Abrufen oder Setzen einer Eigenschaft mit einem via Ausdruck oder Variable ermittelten Namen:
Wert := MeinObjekt.%Ausdruck%
MeinObjekt.%Ausdruck% := Wert
Aufrufen einer Eigenschaft/Methode mit dem direkt geschriebenen Namen Methode:
RückgabeWert := MeinObjekt.Methode(Params)
Aufrufen einer Eigenschaft/Methode mit einem via Ausdruck oder Variable ermittelten Namen:
RückgabeWert := MeinObjekt.%Ausdruck%(Params)
Manchmal werden beim Abrufen oder Zuweisen von Eigenschaften Parameter akzeptiert:
Wert := MeinObjekt.Eigenschaft[Params] MeinObjekt.Eigenschaft[Params] := Wert
Ein Objekt kann auch Indexierung unterstützen: MeinArray[Index]
ruft indirekt die __Item-Eigenschaft von MeinArray
auf und übergibt Index
als Parameter.
Ein direkt geschriebenes Objekt kann in einem Ausdruck verwendet werden, um ein improvisiertes Objekt zu erstellen. Ein direkt geschriebenes Objekt besteht aus zwei geschweiften Klammern ({}
), die eine Liste von kommagetrennten Name-Wert-Paaren umschließen. Jedes Paar besteht aus einem direkt geschriebenen (anführungszeichenlosen) Eigenschaftsnamen und einem Wert (Teilausdruck), die mit einem Doppelpunkt (:
) voneinander getrennt sind. Zum Beispiel:
Koord := {X: 13, Y: 240}
Folgendes ist äquivalent:
Koord := Object() Koord.X := 13 Koord.Y := 240
Jedes Name-Wert-Paar definiert eine Werteigenschaft, mit der Ausnahme, dass Base gesetzt werden kann (mit den gleichen Einschränkungen wie bei einer normalen Zuweisung).
Namenssubstitution ermöglicht die Bestimmung eines Eigenschaftsnamens durch Auswertung eines Ausdrucks oder einer Variable. Zum Beispiel:
Teile := StrSplit("Schlüssel = Wert", "=", " ") Teil := {%Teile[1]%: Teile[2]} MsgBox Teil.Schlüssel
Skripte geben Objekte nicht explizit frei. Sobald die letzte Referenz zu einem Objekt freigegeben ist, wird das Objekt automatisch freigegeben. Eine Referenz, die in einer Variable gespeichert ist, wird automatisch freigegeben, sobald dieser Variable ein anderer Wert zugewiesen wird. Zum Beispiel:
obj := {} ; Erstellt ein Objekt. obj := "" ; Gibt die letzte Referenz frei, wodurch das Objekt freigegeben wird.
Gleichermaßen wird eine Referenz, die in einer Eigenschaft oder einem Array-Element gespeichert ist, freigegeben, wenn diese Eigenschaft oder dieses Array-Element einen anderen Wert zugewiesen bekommt oder aus dem Objekt entfernt wird.
arr := [{}] ; Erstellt ein Array, das ein Objekt enthält. arr[1] := {} ; Erstellt ein zweites Objekt, wodurch das erste Objekt indirekt freigegeben wird. arr.RemoveAt(1) ; Entfernt das zweite Objekt und gibt es frei.
Da alle Referenzen zu einem Objekt freigegeben werden müssen, bevor das Objekt freigegeben werden kann, können Objekte mit Zirkelbezügen nicht automatisch freigegeben werden. Wenn z.B. x.child
auf y
und y.parent
auf x
verweist, genügt es nicht, x
und y
zu leeren, weil das Parent-Objekt noch eine Referenz zum Child-Objekt enthält, und umgekehrt. Um diese Situation zu lösen, entfernen Sie den Zirkelbezug.
x := {}, y := {} ; Zwei Objekte erstellen. x.child := y, y.parent := x ; Einen Zirkelbezug erstellen. y.parent := "" ; Der Zirkelbezug muss entfernt werden, bevor die Objekte freigegeben werden können. x := "", y := "" ; Ohne die obige Zeile würde dies die Objekte nicht freigeben.
Eine ausführlichere Verwendung und Informationen finden Sie unter Referenzzählung.
Obwohl "mehrdimensionale" Arrays nicht unterstützt werden, kann ein Skript mehrere Arrays oder Maps kombinieren. Zum Beispiel:
Gitter := [[1,2,3], [4,5,6], [7,8,9]] MsgBox Gitter[1][3] ; 3 MsgBox Gitter[3][2] ; 8
Ein benutzerdefiniertes Objekt kann mehrdimensionale Unterstützung durch Definition einer __Item-Eigenschaft implementieren. Zum Beispiel:
class Array2D extends Array { __new(x, y) { this.Length := x * y this.Breite := x this.Höhe := y } __Item[x, y] { get => super.Has(this.i[x, y]) ? super[this.i[x, y]] : false set => super[this.i[x, y]] := value } i[x, y] => this.Width * (y-1) + x } Gitter := Array2D(4, 3) Gitter[4, 1] := "#" Gitter[3, 2] := "#" Gitter[2, 2] := "#" Gitter[1, 3] := "#" GitterText := "" Loop Gitter.Höhe { y := A_Index Loop Gitter.Breite { x := A_Index GitterText .= Gitter[x, y] || "-" } GitterText .= "`n" } MsgBox GitterText
Ein echtes Skript sollte eine Fehlerprüfung durchführen und andere Methoden überschreiben, wie z.B. __Enum, um die Enumeration zu unterstützen.
Es gibt zwei verschiedene Möglichkeiten, benutzerdefinierte Objekte zu erstellen:
Metafunktionen können verwendet werden, um das Verhalten eines Objekts genauer zu steuern.
Hinweis: In diesem Abschnitt ist ein Objekt eine Instanz der Object-Klasse. Dieser Abschnitt gilt nicht für COM-Objekte.
Grundsätzlich kann ein neues Objekt jederzeit um Eigenschaften und Methoden (aufrufbare Eigenschaften) erweitert werden. Das folgende Beispiel zeigt, wie ein Objekt mit einer Eigenschaft und einer Methode konstruiert werden kann:
; Ein Objekt erstellen. obj := {} ; Einen Wert speichern. obj.foo := "bar" ; Eine Methode definieren. obj.test := obj_test ; Die Methode aufrufen. obj.test() obj_test(this) { MsgBox this.foo }
Das obige Objekt kann auch mit obj := {foo: "bar"}
erstellt werden. Bei der {Eigenschaft:Wert}-Schreibweise dürfen die Eigenschaften nicht in Anführungszeichen gesetzt werden.
Beim Aufruf von obj.test()
wird obj automatisch an den Anfang der Parameterliste gesetzt. Konventionsgemäß setzt sich der Name der Funktion aus dem "Typ" des Objekts und dem Namen der Methode zusammen, kann aber auch anders lauten.
Im obigen Beispiel kann test, nachdem es definiert wurde, eine andere Funktion oder ein anderer Wert zugewiesen werden; in diesem Fall geht die ursprüngliche Funktion verloren und kann nicht über diese Eigenschaft aufgerufen werden. Alternativ kann eine schreibgeschützte Methode definiert werden:
obj.DefineProp 'test', {call: obj_test}
Siehe auch: DefineProp
Objekte sind prototypenbasiert. Das heißt, dass alle Eigenschaften, die nicht im Objekt selbst definiert sind, stattdessen in der Basis des Objekts definiert sein können. Dies wird als Vererbung durch Delegation oder differentielle Vererbung bezeichnet, da ein Objekt nur die Teile implementieren kann, die sich vom Objekt unterscheiden, und den Rest an seine Basis delegiert.
Obwohl ein Basisobjekt allgemein auch als Prototyp bekannt ist, verwenden wir "Prototyp einer Klasse" für das Objekt, auf dem jede Instanz der Klasse basiert, und "Basis" für das Objekt, auf dem eine Instanz basiert.
Das Objektdesign von AutoHotkey wurde hauptsächlich von JavaScript und Lua und ein wenig von C# beeinflusst. Wir verwenden obj.base
anstelle von JavaScripts obj.__proto__
und cls.Prototype
anstelle von JavaScripts func.prototype
. (Klassenobjekte werden anstelle von Konstruktorfunktionen verwendet.)
Die Basis eines Objekts wird auch verwendet, um seinen Typ oder seine Klasse zu identifizieren. Zum Beispiel erstellt x := []
ein Objekt basierend auf Array.Prototype
, d.h. die Ausdrücke x is Array
und x.HasBase(Array.Prototype)
sind True und type(x)
gibt "Array" zurück. Der Prototyp jeder Klasse basiert auf dem Prototyp ihrer Basisklasse, daher ist x.HasBase(Object.Prototype)
ebenfalls True.
Jede Instanz von Object oder eine abgeleitete Klasse kann ein Basisobjekt sein, aber ein Objekt kann nur als Basis eines Objekts mit dem gleichen nativen Typ zugewiesen werden. Dadurch wird sichergestellt, dass interne Methoden immer den nativen Typ eines Objekts identifizieren können und dass sie nur mit Objekten arbeiten, die die korrekte binäre Struktur aufweisen.
Basisobjekte können auf zwei verschiedene Arten definiert werden:
Ein Basisobjekt kann der Base-Eigenschaft eines anderen Objekts zugewiesen werden, aber normalerweise wird die Basis eines Objekts implizit beim Erstellen festgelegt.
Jedes Objekt kann als Basis eines anderen Objekts mit demselben nativen Typ verwendet werden. Das folgende Beispiel baut auf dem vorherigen Beispiel unter Ad Hoc auf (kombinieren Sie beide vor der Ausführung):
anderesObj := {} anderesObj.base := obj anderesObj.test()
anderesObj erbt in diesem Fall foo und test von obj. Diese Vererbung ist dynamisch, d.h. wenn obj.foo
geändert wird, wird diese Änderung durch anderesObj.foo
widergespiegelt. Wenn das Skript anderesObj.foo
einen Wert zuweist, wird dieser Wert in anderesObj gespeichert; weitere Änderungen an obj.foo
hätten keinen Effekt auf anderesObj.foo
. Beim Aufruf von anderesObj.test()
enthält dessen this-Parameter eine Referenz zu anderesObj statt zu obj.
Unter einer Klasse (auch Objekttyp genannt) versteht man in der objektorientierten Programmierung ein abstraktes Modell bzw. einen Bauplan für eine Reihe von ähnlichen Objekten. Wikipedia
Allgemein ausgedrückt ist eine Klasse eine Gruppe oder Kategorie von Dingen, die gemeinsame Eigenschaften oder Attribute haben. In AutoHotkey definiert class
Eigenschaften, die von Instanzen der Klasse gemeinsam verwendet werden (und Methoden, die aufrufbare Eigenschaften sind). Eine Instanz ist schlicht ein Objekt, das Eigenschaften von der Klasse erbt und typischerweise auch als Teil dieser Klasse verstanden werden kann (z.B. mit dem Ausdruck Instanz is KlasseName
). Instanzen werden typischerweise durch den Aufruf von KlasseName() erzeugt.
Da Objekte dynamisch und prototypenbasiert sind, besteht jede Klasse aus zwei Teilen:
static
fehlt.static
und alle verschachtelten Klassen. Diese gelten nicht für eine bestimmte Instanz und können verwendet werden, indem via Name auf die Klasse selbst verwiesen wird.Das Folgende zeigt die meisten Elemente einer Klassendefinition:
class KlasseName extends BasisklasseName { InstanzVar := Ausdruck static KlasseVar := Ausdruck class VerschachtelteKlasse { ... } Methode() { ... } static Methode() { ... } Eigenschaft[Parameter] ; Eckige Klammern nur verwenden, wenn Parameter vorhanden sind. { get { return Eigenschaftswert } set { Speichere oder verarbeite value } } KurzeEigenschaft { get => Ausdruck zum Ermitteln des Eigenschaftswertes set => Ausdruck zum Speichern oder Verarbeiten von value } KürzereEigenschaft => Ausdruck zum Ermitteln des Eigenschaftswertes }
Dies bewirkt, dass beim Laden des Skripts ein Class-Objekt konstruiert und in eine globale Konstante (schreibgeschützte Variable) namens KlasseName gespeichert wird. Wenn extends BasisklasseName
vorhanden ist, muss BasisklasseName der vollständige Name einer anderen Klasse sein. Der vollständige Name jeder Klasse ist in KlasseName.Prototype.__Class
gespeichert.
Da der Zugriff auf die Klasse selbst über eine Variable erfolgt, kann der Klassenname nicht verwendet werden, um im selben Kontext sowohl die Klasse zu referenzieren als auch eine separate Variable zu erstellen (um z.B. eine Instanz der Klasse zu enthalten). Zum Beispiel wird box := Box()
nicht funktionieren, da sowohl box
als auch Box
in dasselbe aufgelöst werden. Der Versuch, eine Top-Level-Klasse (nicht verschachtelte Klasse) auf diese Weise neu zuzuweisen, führt zu einem Ladezeitfehler.
In dieser Dokumentation bezieht sich das Wort "Klasse" in der Regel auf ein Klassenobjekt, das mit dem Schlüsselwort class
erstellt wurde.
Klassendefinitionen können Variablendeklarationen, Methodendefinitionen und Unterklassendefinitionen enthalten.
Eine Instanzvariable ist eine Variable, von der jede Instanz der Klasse eine eigene Kopie hat. Sie werden wie normale Zuweisungen deklariert und verhalten sich auch wie solche, aber ohne Angabe des Präfixes this.
(nur direkt im Körper der Klasse):
InstanzVar := Ausdruck
Solche Deklarationen werden jedes Mal ausgewertet, wenn eine neue Instanz der Klasse mit KlasseName() erstellt wird, nachdem alle Basisklassendeklarationen ausgewertet wurden, aber bevor __New aufgerufen wird. Dies wird erreicht, indem automatisch eine Methode namens __Init erstellt wird, die einen Aufruf von super.__Init()
enthält, und jede Deklaration in diese eingefügt wird. Daher darf eine einzelne Klassendefinition weder eine __Init-Methode noch eine Instanzvariablendeklaration enthalten.
Ausdruck kann mit this
auf andere Instanzvariablen und Methoden zugreifen. Globale Variablen können gelesen, aber nicht zugewiesen werden. Eine zusätzliche Zuweisung (oder die Verwendung des Referenzoperators) innerhalb des Ausdrucks erzeugt in der Regel eine Variable, die lokal für die __Init-Methode ist. Zum Beispiel bewirkt x := y := 1
, dass this.x
und eine lokale Variable y
gesetzt wird (die nach der Auswertung aller Initialisierungen wieder freigegeben wird).
Um auf eine Instanzvariable zuzugreifen (auch innerhalb einer Methode), geben Sie immer das Zielobjekt an, z.B. this.InstanzVar
.
Deklarationen wie x.y := z
werden ebenfalls unterstützt, sofern vorher x
in dieser Klasse definiert wurde. Zum Beispiel bewirkt x := {}, x.y := 42
, dass x
deklariert und außerdem this.x.y
initialisiert wird.
Statische bzw. Klassenvariablen gehören nur der Klasse selbst, aber ihre Werte können von Unterklassen geerbt werden. Solche Variablen müssen mit dem Schlüsselwort "static" deklariert werden:
static KlasseVar := Ausdruck
Diese Deklarationen werden nur einmal beim Initialisieren der Klasse ausgewertet. Zu diesem Zweck wird automatisch eine statische Methode namens __Init definiert.
Jede Deklaration fungiert wie eine normale Eigenschaftszuweisung, mit dem Klassenobjekt als Ziel. Ausdruck wird genauso interpretiert wie die Instanzvariablen, außer dass this
auf die Klasse selbst verweist.
Um einer Klassenvariable an anderer Stelle etwas zuzuweisen, geben Sie immer das Klassenobjekt an, z.B. KlasseName.KlasseVar := Wert
. Wenn eine Unterklasse keine Eigenschaft mit diesem Namen hat, kann auch mit Unterklasse.KlasseVar
der Wert abgerufen werden, d.h. wenn der Wert eine Referenz zu einem Objekt ist, teilen sich die Unterklassen standardmäßig dieses Objekt. Allerdings würde Unterklasse.KlasseVar := y
den Wert in Unterklasse speichern, nicht in KlasseName.
Deklarationen wie static x.y := z
werden ebenfalls unterstützt, sofern vorher x
in dieser Klasse definiert wurde. Zum Beispiel bewirkt static x := {}, x.y := 42
, dass x
deklariert und außerdem KlasseName.x.y
initialisiert wird. Da Prototype implizit in jeder Klasse definiert ist, können mit static Prototype.geteilterWert := 1
Werte gesetzt werden, die dynamisch von allen Instanzen der Klasse geerbt werden (bis sie durch eine Eigenschaft der Instanz selbst "überschrieben" werden).
Verschachtelte Klassendefinitionen ermöglichen es, ein Klassenobjekt mit einer statischen bzw. Klassenvariable der äußeren Klasse zu assoziieren, anstatt mit einer separaten globalen Variablen. Im obigen Beispiel konstruiert class VerschachtelteKlasse
ein Class-Objekt und speichert es in KlasseName.VerschachtelteKlasse
. Unterklassen können VerschachtelteKlasse erben oder es mit ihrer eigenen verschachtelten Klasse überschreiben (in diesem Fall kann mit WelcheKlasse.VerschachtelteKlasse()
die entsprechende Klasse instanziiert werden).
class VerschachtelteKlasse { ... }
Das Verschachteln einer Klasse impliziert keine besondere Beziehung zur äußeren Klasse. Weder wird die verschachtelte Klasse automatisch instanziiert, noch haben Instanzen der verschachtelten Klasse eine Verbindung zu einer Instanz der äußeren Klasse, es sei denn, das Skript stellt diese Verbindung explizit her.
Jede verschachtelte Klassendefinition erzeugt eine dynamische Eigenschaft mit Get- und Call-Akzessorfunktionen anstelle einer einfachen Werteigenschaft. Damit soll das folgende Verhalten unterstützt werden (wobei die Klasse X die verschachtelte Klasse Y enthält):
X.Y()
übergibt X nicht an X.Y.Call
und letztlich an __New
, was sonst passieren würde, da dies das normale Verhalten für Funktionsobjekte ist, die als Methoden aufgerufen werden (so wie die verschachtelte Klasse hier verwendet wird).X.Y := 1
ist standardmäßig ein Fehler (die Eigenschaft ist schreibgeschützt).Methodendefinitionen sehen genauso aus wie Funktionsdefinitionen. Jede Methodendefinition erstellt ein Func mit einem versteckten ersten Parameter namens this
und definiert eine Eigenschaft, die zum Aufrufen der Methode oder zum Abrufen ihres Funktionsobjekts verwendet wird.
Es gibt zwei Arten von Methoden:
this
auf eine Instanz der Klasse.static
voran. Diese sind an das Klassenobjekt selbst gebunden, werden aber auch von Unterklassen geerbt, so dass this
entweder auf die Klasse selbst oder auf eine Unterklasse verweist.Die Methodendefinition unten erstellt eine Eigenschaft vom gleichen Typ wie Ziel.DefineProp('Methode', {call: funcObj})
. Standardmäßig gibt Ziel.Methode
funcObj zurück, und der Versuch, Ziel.Methode
etwas zuzuweisen, löst einen Fehler aus. Diese Standardverhalten können durch Definieren einer Eigenschaft oder Aufrufen von DefineProp überschrieben werden.
Methode() { ... }
Mit der Fat-Arrow-Syntax kann eine einzeilige Methode definiert werden, die ein Ausdruck zurückgibt:
Methode() => Ausdruck
Innerhalb einer Methode oder eines Eigenschafts-Getters/Setters kann das Schlüsselwort super
anstelle von this
verwendet werden, um auf Superklassenversionen von Methoden oder Eigenschaften zuzugreifen, die in einer abgeleiteten Klasse überschrieben werden. Zum Beispiel würde super.Methode()
in der oben definierten Klasse typischerweise eine Version von Methode aufrufen, die innerhalb von BasisklasseName definiert wurde. Hinweis:
super.Methode()
ruft immer indirekt die Basis der Klasse oder des Prototypobjekts auf, das mit der ursprünglichen Definition der aktuellen Methode assoziiert ist, auch dann, wenn this
von einer Unterklasse dieser Klasse oder von einer ganz anderen Klasse abgeleitet wurde.super.Methode()
übergibt implizit this
als ersten (versteckten) Parameter.super.Methode()
meistens äquivalent zu (KlasseName.Prototype.base.Methode)(this)
(aber ohne Prototype, wenn Methode statisch ist). Allerdings wird KlasseName.Prototype
beim Laden des Skripts aufgelöst.Nach dem Schlüsselwort super
muss eines der folgenden Symbole folgen: .[(
super()
ist äquivalent zu super.call()
.
Eine Eigenschaftsdefinition erzeugt eine dynamische Eigenschaft, die eine Methode aufruft, anstatt nur einen Wert zu speichern oder zurückzugeben.
Eigenschaft[Parameter] { get { return Eigenschaftswert } set { Speichere oder verarbeite value } }
Eigenschaft ist der Name der Eigenschaft, der für ihren indirekten Aufruf verwendet wird. Zum Beispiel bewirkt obj.Eigenschaft
, dass get aufgerufen wird, und obj.Eigenschaft := Wert
, dass set aufgerufen wird. Innerhalb von get oder set bezieht sich this
auf das Objekt, das gerade indirekt aufgerufen wird. Innerhalb von set enthält value
den Wert, der gerade zugewiesen wird.
Die Definition und Übergabe von Parametern erfolgt rechts vom Eigenschaftsnamen in eckigen Klammern - diese sollten jedoch weggelassen werden, wenn keine Parameter vorhanden sind (siehe unten). Abgesehen von der Verwendung eckiger Klammern werden die Parameter von Eigenschaften genauso definiert wie die Parameter von Methoden - optionale, variadische und ByRef-Parameter werden unterstützt.
Wenn eine Eigenschaft mit Parametern indirekt aufgerufen wird, aber keine definiert sind, werden Parameter automatisch an die __Item-Eigenschaft des von get zurückgegebenen Objekts weitergeleitet. Zum Beispiel hätte this.Eigenschaft[x]
denselben Effekt wie (this.Eigenschaft)[x]
oder y := this.Eigenschaft, y[x]
. Leere Klammern (this.Eigenschaft[]
) führen dazu, dass die __Item-Eigenschaft des Wertes von Eigenschaft immer indirekt aufgerufen wird, aber ein variadischer Aufruf wie this.Eigenschaft[Args*]
hat diesen Effekt nur, wenn die Anzahl der Parameter ungleich Null ist.
Um statische Eigenschaften zu definieren, stellen Sie dem Eigenschaftsnamen das separate Schlüsselwort static
voran. In diesem Fall verweist this
auf die Klasse selbst oder auf eine Unterklasse.
Der Rückgabewert von set wird ignoriert. Zum Beispiel wird Wert := obj.Eigenschaft := 42
immer Wert := 42
zuweisen, egal was die Eigenschaft macht, es sei denn, sie löst eine Ausnahme aus oder beendet den Thread.
Jede Klasse kann eine oder beide Hälften einer Eigenschaft definieren. Wenn eine Klasse eine Eigenschaft überschreibt, kann sie super.Eigenschaft
verwenden, um auf die von ihrer Basisklasse definierte Eigenschaft zuzugreifen. Wenn Get oder Set nicht definiert ist, kann es von einem Basisobjekt geerbt werden. Wenn Get undefiniert ist, kann die Eigenschaft einen Wert zurückgeben, der von einer Basis geerbt wurde. Wenn Set in diesem und allen Basisobjekten undefiniert ist (oder von einer geerbten Werteigenschaft verschleiert wird), löst der Versuch, die Eigenschaft zu setzen, eine Ausnahme aus.
Eine Eigenschaftsdefinition mit sowohl Get als auch Set erstellt faktisch zwei separate Funktionen, die weder lokale oder statische Variablen noch verschachtelte Funktionen gemeinsam nutzen. Analog zu den Methoden hat jede Funktion einen versteckten Parameter namens this
, und Set hat einen zweiten versteckten Parameter namens value
. Explizit definierte Parameter erfolgen danach.
Während eine Eigenschaftsdefinition die Get- und Set-Akzessorfunktionen für eine Eigenschaft auf die gleiche Weise wie DefineProp definiert, definiert eine Methodendefinition die Call-Akzessorfunktion. Jede Klasse kann eine Eigenschaftsdefinition und eine Methodendefinition gleichen Namens enthalten. Wenn eine Eigenschaft ohne Call-Akzessorfunktion (eine Methode) aufgerufen wird, wird Get ohne Parameter indirekt aufgerufen und das Ergebnis anschließend als Methode aufgerufen.
Mit der Fat-Arrow-Syntax kann ein Eigenschaft-Getter oder -Setter definiert werden, der ein Ausdruck zurückgibt:
KurzeEigenschaft[Parameter] { get => Ausdruck zum Ermitteln des Eigenschaftswertes set => Ausdruck zum Speichern oder Verarbeiten von value }
Wenn nur ein Getter definiert wird, können die geschweiften Klammern und get
weggelassen werden:
KürzereEigenschaft[Parameter] => Ausdruck zum Ermitteln des Eigenschaftswertes
In beiden Fällen müssen die eckigen Klammern weggelassen werden, es sei denn, Parameter sind definiert.
__Enum(AnzahlVars)
Die __Enum-Methode wird aufgerufen, wenn das Objekt an eine For-Schleife übergeben wird. Diese Methode sollte einen Enumerator zurückgeben, der Elemente im Objekt zurückgibt, wie z.B. Array-Elemente. Wenn es undefiniert bleibt, kann das Objekt nicht direkt an eine For-Schleife übergeben werden, es sei denn, es hat eine Enumerator-kompatible Call-Methode.
AnzahlVars enthält die Anzahl der Variablen, die an die For-Schleife übergeben wurden. Wenn AnzahlVars 2 ist, wird erwartet, dass der Enumerator dem ersten Parameter den Schlüssel oder Index eines Elements und dem zweiten Parameter den Wert zuweist. Jeder Schlüssel oder Index sollte als Parameter der __Item-Eigenschaft akzeptiert werden. Dies befähigt DBGp-basierte Debugger, ein bestimmtes Element abzurufen oder zu setzen, nachdem sie durch indirekten Aufruf des Enumerators aufgelistet wurden.
Die __Item-Eigenschaft wird indirekt aufgerufen, wenn der Indexierungsoperator (Arraysyntax) in Verbindung mit dem Objekt verwendet wird. Im folgenden Beispiel wird die Eigenschaft als statisch deklariert, so dass der Indexierungsoperator auf die Env-Klasse selbst angewendet werden kann. Ein weiteres Beispiel finden Sie unter Array2D.
class Env { static __Item[name] { get => EnvGet(name) set => EnvSet(name, value) } } Env["PATH"] .= ";" A_ScriptDir ; Betrifft nur dieses Skript und Unterprozesse. MsgBox Env["PATH"]
__Item
ist quasi ein Standardeigenschaftsname (falls eine solche Eigenschaft definiert wurde):
object[params]
ist äquivalent zu object.__Item[params]
, wenn Parameter vorhanden sind.object[]
ist äquivalent zu object.__Item
.Zum Beispiel:
obj := {} obj[] := Map() ; Äquivalent zu obj.__Item := Map() obj["base"] := 10 MsgBox obj.base = Object.prototype ; True MsgBox obj["base"] ; 10
Hinweis: Wenn ein expliziter Eigenschaftsname mit leeren eckigen Klammern kombiniert wird, wie in obj.prop[]
, wird dies als zwei separate Operationen behandelt: Zuerst obj.prop
abrufen und dann die Standardeigenschaft des Ergebnisses indirekt aufrufen. Dies ist Teil der Sprachsyntax und somit objektunabhängig.
Jedes Mal, wenn ein Objekt mit der Standardimplementierung von KlasseName() erstellt wird, wird die __New
-Methode des neuen Objekts aufgerufen, um eine benutzerdefinierte Initialisierung zu ermöglichen. Alle Parameter, die an KlasseName()
übergeben werden, werden an __New
weitergeleitet, so dass sie den ursprünglichen Inhalt des Objekts oder die Art und Weise, wie es konstruiert wird, beeinflussen können. Bei Zerstörung des Objekts wird __Delete
aufgerufen. Zum Beispiel:
m1 := GMem(0, 10) m2 := {base: GMem.Prototype}, m2.__New(0, 30) ; Hinweis: Für allgemeine Speicherreservierungen stattdessen Buffer() verwenden. class GMem { __New(aFlags, aGröße) { this.ptr := DllCall("GlobalAlloc", "UInt", aFlags, "Ptr", aGröße, "Ptr") if !this.ptr throw MemoryError() MsgBox "Neues GMem von " aGröße " Bytes auf Adresse " this.ptr "." } __Delete() { MsgBox "GMem auf Adresse " this.ptr " löschen." DllCall("GlobalFree", "Ptr", this.ptr) } }
__Delete wird nicht für Objekte aufgerufen, die eine Eigenschaft namens "__Class" haben. Prototypobjekte haben diese Eigenschaft standardmäßig.
Wenn ein Ausnahme- oder Laufzeitfehler bei der Ausführung von __Delete ausgelöst und nicht innerhalb von __Delete behandelt wird, verhält es sich so, als wäre __Delete von einem neuen Thread aufgerufen worden. Das heißt, dass ein Fehlerdialogfenster angezeigt wird und __Delete zurückkehrt, aber der Thread nicht beendet wird (es sei denn, er wurde bereits beendet).
Wenn das Skript auf irgendeine Weise direkt terminiert wird, z.B. über das Tray-Menü oder ExitApp, bekommen alle Funktionen, die noch nicht zu ihrem Aufrufer zurückgekehrt sind, keine Chance, dies jemals zu tun. Daher werden alle Objekte, die von lokalen Variablen dieser Funktionen referenziert werden, nicht freigegeben, was dazu führt, dass __Delete nicht aufgerufen wird.
Bei Beendigung des Skripts werden Objekte, die in globalen und statischen Variablen enthalten sind, automatisch in einer willkürlichen, von der Implementierung festgelegten Reihenfolge freigegeben. Wenn __Delete während dieses Vorgangs aufgerufen wird, können einige globale oder statische Variablen bereits freigegeben worden sein, aber alle Referenzen, die das Objekt selbst enthält, sind noch gültig. Daher ist es am besten, wenn __Delete völlig eigenständig ist und nicht auf globale oder statische Variablen angewiesen ist.
Jede Klasse wird automatisch initialisiert, wenn eine Referenz zur Klasse erstmals ausgewertet wird. Wenn z.B. MeineKlasse noch nicht initialisiert wurde, würde MeineKlasse.MeineEigenschaft
bewirken, dass die Klasse initialisiert wird, bevor die Eigenschaft abgerufen wird. Die Initialisierung umfasst den Aufruf von zwei statischen Methoden: __Init und __New.
static __Init
wird automatisch für jede Klasse definiert und beginnt immer mit einer Referenz zur Basisklasse, falls angegeben, um sicherzustellen, dass sie initialisiert ist. Statische bzw. Klassenvariablen und verschachtelte Klassen werden in der Reihenfolge initialisiert, in der sie definiert wurden, außer wenn eine verschachtelte Klasse während der Initialisierung einer vorherigen Variable oder Klasse referenziert wird.
Wenn die Klasse eine static __New
-Methode definiert oder erbt, wird diese unmittelbar nach __Init aufgerufen. Es ist wichtig zu beachten, dass __New einmal für die Klasse, in der es definiert ist, und einmal für jede Unterklasse, die keine eigene definiert (oder die super.__New()
aufruft), aufgerufen werden kann. Dies kann verwendet werden, um allgemeine Initialisierungsaufgaben für jede Unterklasse durchzuführen oder um Unterklassen vor ihrer Verwendung in irgendeiner Weise zu modifizieren.
Wenn static __New
nicht mit abgeleiteten Klassen arbeiten soll, kann dies durch die Überprüfung des Wertes von this
vermieden werden. In einigen Fällen kann es ausreichend sein, dass die Methode sich selbst löscht, wie z.B. bei this.DeleteProp('__New')
; allerdings könnte die erste Ausführung von __New für eine Unterklasse sein, wenn diese in der Basisklasse verschachtelt ist oder bei der Initialisierung einer statischen bzw. Klassenvariable referenziert wird.
Eine Klassendefinition bewirkt auch, dass die Klasse referenziert wird. Mit anderen Worten, wenn die Ausführung während der Startphase des Skripts eine Klassendefinition erreicht, werden __Init und __New automatisch aufgerufen, es sei denn, die Klasse wurde bereits vom Skript referenziert. Wenn die Ausführung jedoch daran gehindert wird, die Klassendefinition zu erreichen, z.B. durch return
oder eine Endlosschleife, wird die Klasse erst initialisiert, wenn sie referenziert wird.
Wenn die automatische Initialisierung einmal begonnen hat, wird sie für dieselbe Klasse nicht erneut durchgeführt. Dies ist grundsätzlich kein Problem, es sei denn, mehrere Klassen referenzieren sich gegenseitig. Nehmen wir zum Beispiel die folgenden zwei Klassen. Wenn A
zuerst initialisiert wird, bewirkt die Auswertung von B.SharedArray
(A1), dass B
initialisiert wird, bevor der Wert abgerufen und zurückgegeben wird, aber A.SharedValue
(A3) ist undefiniert und bewirkt keine Initialisierung von A
, da diese bereits im Gange ist. Mit anderen Worten, wenn A
erstmals verwendet oder initialisiert wird, ist die Reihenfolge A1 bis A3, sonst B1 bis B4:
MsgBox A.SharedArray.Length MsgBox B.SharedValue class A { static SharedArray := B.SharedArray ; A1 ; B3 static SharedValue := 42 ; B4 } class B { static SharedArray := StrSplit("XYZ") ; A2 ; B1 static SharedValue := A.SharedValue ; A3 (Error) ; B2 }
class KlasseName { __Get(Name, Params) __Set(Name, Params, Wert) __Call(Name, Params) }
Der Name der Eigenschaft oder Methode.
Ein Array von Parametern. Dies umfasst nur die Parameter zwischen ()
oder []
, kann also leer sein. Die Metafunktion soll Fälle wie x.y[z]
behandeln, in denen x.y
undefiniert ist.
Der Wert, der zugewiesen wird.
Metafunktionen definieren, was passieren soll, wenn eine undefinierte Eigenschaft oder Methode indirekt aufgerufen wird. Wenn z.B. obj.unk
keinen Wert zugewiesen bekommen hat, ruft es indirekt die __Get-Metafunktion auf. obj.unk := Wert
bewirkt dagegen den indirekten Aufruf von __Set und obj.unk()
den indirekten Aufruf von __Call.
Eigenschaften und Methoden können im Objekt selbst oder in einem seiner Basisobjekte definiert werden. Damit für jede Eigenschaft eine Metafunktion aufgerufen wird, sollte die Definition von Eigenschaften grundsätzlich vermieden werden. Interne Eigenschaften wie Base können mit einer Eigenschaftsdefinition oder DefineProp überschrieben werden.
Wenn eine Metafunktion definiert ist, muss sie die gewünschte Standardaktion ausführen. Zum Beispiel kann Folgendes erwartet werden:
Jedes aufrufbare Objekt kann als Metafunktion verwendet werden, wenn es der entsprechenden Eigenschaft zugewiesen wird.
Metafunktionen werden in den folgenden Fällen nicht aufgerufen:
x[y]
: Die Verwendung von eckigen Klammern ohne Eigenschaftsnamen ruft indirekt nur die __Item-Eigenschaft auf.x()
: Der Aufruf des Objekts selbst ruft indirekt nur die Call
-Methode auf. Dies umfasst interne Aufrufe durch interne Funktionen wie SetTimer und Hotkey.__Call
nicht aus.Die Eigenschaftssyntax und DefineProp ermöglichen die Definition von Eigenschaften, die bei jeder Auswertung einen Wert ermitteln, allerdings muss jede Eigenschaft im Voraus definiert sein. Mit __Get und __Set können hingegen Eigenschaften implementiert werden, die erst zum Zeitpunkt ihres indirekten Aufrufs bekannt sind.
Zum Beispiel kann ein "Proxy-Objekt" mit Eigenschaften erstellt werden, die den aktuellen Wert über das Netzwerk (oder über einen anderen Kanal) abfragen. Ein Remote-Server sendet eine Antwort zurück, die den Wert der Eigenschaft enthält, und das Proxy-Objekt übergibt den Wert an seinen Aufrufer. Selbst wenn der Name jeder Eigenschaft im Voraus bekannt wäre, würde es keinen Sinn machen, jede Eigenschaft einzeln in der Proxy-Klasse zu definieren, da jede Eigenschaft dasselbe bewirkt (eine Netzwerkanfrage senden). Metafunktionen empfangen den Namen der Eigenschaft als Parameter und sind daher eine gute Lösung für dieses Problem.
Primitive Werte wie Zeichenketten und Zahlen können keine eigenen Eigenschaften und Methoden haben. Allerdings unterstützen primitive Werte die gleiche Art der Delegation wie Objekte. Das heißt, dass jeder Eigenschafts- oder Methodenaufruf bei einem primitiven Wert an ein vordefiniertes Prototypobjekt delegiert wird, auf das auch über die Prototype-Eigenschaft der entsprechenden Klasse zugegriffen werden kann. Die folgenden Klassen beziehen sich auf primitive Werte:
Obwohl die Überprüfung der Type-Zeichenkette in der Regel schneller ist, kann der Typ eines Wertes auch getestet werden, indem geprüft wird, ob der Wert eine Basis hat. Zum Beispiel ist n.HasBase(Number.Prototype)
oder n is Number
True, wenn n ein reiner Integer oder eine reine Floating-Point-Zahl ist, aber False, wenn n eine numerische Zeichenkette ist, da String nicht von Number ableitet. IsNumber(n)
hingegen ist True, wenn n eine Zahl oder numerische Zeichenkette ist.
ObjGetBase und die Base-Eigenschaft geben ggf. eines der vordefinierten Prototypobjekte zurück.
Beachten Sie, dass x is Any
normalerweise True für jeden Wert innerhalb der Typenhierarchie von AutoHotkey ist, aber False für COM-Objekte ist.
Eigenschaften und Methoden können für alle Werte eines bestimmten Typs hinzugefügt werden, indem das Prototypobjekt dieses Typs geändert wird. Da jedoch ein primitiver Wert kein Object ist und keine eigenen Eigenschaften oder Methoden haben kann, werden die primitiven Prototypobjekte nicht von Object.Prototype
ableiten. Mit anderen Worten, es ist standardmäßig nicht möglich, auf Methoden wie DefineProp und HasOwnProp zuzugreifen. Diese können indirekt aufgerufen werden. Zum Beispiel:
DefProp := {}.DefineProp DefProp( "".base, "Length", { get: StrLen } ) MsgBox A_AhkPath.length " == " StrLen(A_AhkPath)
Obwohl primitive Werte Werteigenschaften von ihrem Prototyp erben können, wird eine Ausnahme ausgelöst, wenn das Skript versucht, eine Werteigenschaft bei einem primitiven Wert zu setzen. Zum Beispiel:
"".base.test := 1 ; Bitte nicht nachmachen. MsgBox "".test ; 1 "".test := 2 ; Fehler: Eigenschaft ist schreibgeschützt.
Obwohl __Set und Eigenschafts-Setter verwendet werden können, sind sie nicht nützlich, da primitive Werte als unveränderlich zu betrachten sind.
AutoHotkey verwendet einen einfachen Referenzzählmechanismus, um belegte Ressourcen eines Objekts automatisch freizugeben, wenn es nicht länger im Skript referenziert wird. Skriptautoren sollten diesen Mechanismus nicht explizit aufrufen, es sei denn, sie arbeiten direkt mit unverwalteten Objektpointern.
Temporäre Referenzen, die von Funktionen, Methoden oder Operatoren innerhalb eines Ausdrucks zurückgegeben werden, werden erst freigegeben, wenn die Auswertung dieses Ausdrucks abgeschlossen oder abgebrochen wurde. Im folgenden Beispiel wird das neue GMem-Objekt erst nach Abschluss von MsgBox freigegeben:
MsgBox DllCall("GlobalSize", "ptr", GMem(0, 20).ptr, "ptr") ; 20
Hinweis: In diesem Beispiel kann .ptr
weggelassen werden, da der Ptr-Argumenttyp Objekte mit einer Ptr
-Eigenschaft zulässt. Allerdings funktioniert das oben gezeigte Muster auch mit anderen Eigenschaftsnamen.
Um Code beim Freigeben der letzten Referenz zu einem Objekt auszuführen, implementieren Sie die __Delete-Metafunktion.
Bekannte Einschränkungen:
Obwohl das Betriebssystem den vom Objekt belegten Speicher bei Beendigung des Programms zurückfordern wird, wird __Delete erst aufgerufen, wenn alle Referenzen zum Objekt freigegeben wurden. Dies kann wichtig sein, wenn es andere Ressourcen freigibt, die nicht automatisch vom Betriebssystem zurückgefordert werden, wie z.B. temporäre Dateien.
In seltenen Fällen kann es erforderlich sein, dass ein Objekt via DllCall an einen externen Code übergeben werden muss oder dass ein Objekt in eine binäre Datenstruktur für späteren Gebrauch gespeichert werden muss. Die Adresse eines Objekts kann via Adresse := ObjPtr(meinObjekt)
abgerufen werden, allerdings werden dadurch effektiv zwei Referenzen zum Objekt erzeugt, während das Programm selbst nur die eine Referenz in meinObjekt kennt. Das Objekt wird gelöscht, sobald die letzte bekannte Referenz zum Objekt freigegeben wird. Folglich muss das Skript dem Objekt mitteilen, dass es eine Referenz erhalten hat. Dies kann wie folgt erreicht werden (die folgenden zwei Zeilen sind äquivalent):
ObjAddRef(Adresse := ObjPtr(meinObjekt)) Adresse := ObjPtrAddRef(meinObjekt)
Außerdem muss das Objekt informiert werden, wenn das Skript mit dieser Referenz fertig ist:
ObjRelease(Adresse)
Generell sollte jede neue Kopie einer Objektadresse als separate Referenz zum Objekt behandelt werden, d.h. das Skript sollte ObjAddRef aufrufen, wenn es eine Kopie erhält, und sofort ObjRelease aufrufen, bevor es eine Kopie verliert. Zum Beispiel sollte immer ObjAddRef aufgerufen werden, wenn eine Adresse mit so etwas wie x := Adresse
kopiert wird. Ebenso sollte das Skript ObjRelease aufrufen, wenn es mit x fertig ist (oder dabei ist, den Wert von x zu überschreiben).
Mit der ObjFromPtr-Funktion kann eine Adresse in eine geeignete Referenz umgewandelt werden:
meinObjekt := ObjFromPtr(Adresse)
ObjFromPtr vermutet, dass Adresse eine gezählte Referenz ist, und nimmt sie in Besitz. Mit anderen Worten, meinObjekt := ""
bewirkt, dass die Referenz, ursprünglich durch Adresse repräsentiert, freigegeben wird. Danach muss Adresse als ungültig betrachtet werden. Um stattdessen eine neue Referenz zu erstellen, verwenden Sie eine der folgenden Möglichkeiten:
ObjAddRef(Adresse), meinObjekt := ObjFromPtr(Adresse) meinObjekt := ObjFromPtrAddRef(Adresse)