Hallo Robert
Der Unterschied zwischen C++ und JavaScript in Bezug auf
const
ist hier, dass in C++ das gesamte Objekt als konstant angesehen wird, während es in JavaScript anscheinend nur die Verwaltungsinformationen des Objekts sind, wobei Attribute (wie die Länge) offensichtlich nicht zu diesen Informationen gehören.
Das ist größtenteils richtig. Konstant ist in JavaScript tatsächlich nur die Bindung zwischen Bezeichner und Wert, wobei der Wert bei Objekten intern – wie dedlfix bereits sagte – bloß ein Verweis auf eine Speicherstelle ist. Solange auf dieselbe Speicherstelle verwiesen wird, ist der Vertrag nicht verletzt. Du musst aber unterscheiden zwischen den Verwaltungsinformationen der Konstanten und denen des Objekts. Das sind zwei voneinander unabhängige Entitäten.
Eine Konstante ist aus Implementierungssicht nicht viel Mehr als ein Eintrag in einer Liste, die mit der lexikalischen Umgebung verknüpft ist, in der die Konstante deklariert wurde. Dabei handelt es sich selbst wieder um eine Verwaltungsstruktur, die relevant ist für die Namensauflösung innerhalb des Programms. Die Verwaltungsinformationen für ein Objekt, wie zum Beispiel das Verzeichnis der eigenen Eigenschaften oder der Verweis auf den Prototypen des Objekts, haben damit abgesehen von ein oder zwei Querverweisen nicht viel zu tun. Das wird vielleicht klarer, wenn wir uns ein kleines Beispiel ansehen.
// Global scope
const a = {};
{
// Local scope of block statement
const b = a;
}
Was passiert hier? Zunächst mal wird im globalen Scope eine Konstante deklariert. Dabei wird ein leeres Objekt erzeugt und ein Verweis auf das Objekt wird an den Bezeichner der Konstante gebunden. In JavaScript beschrieben haben wir nun stark vereinfacht folgende Struktur für die globale lexikalische Umgebung:
const OuterEnvironment = Symbol();
// Information about lexical environment
let GlobalEnvironmentRecord = {
// This is the outermost lexical environment
[OuterEnvironment]: null,
// Binding for constant
a: {
value: '[[object reference]]',
immutable: true
}
};
Es wird an entsprechender Stelle ein Eintrag hinterlegt, der den Namen der Konstanten mit dem Wert verknüpft und ein paar Metainformationen bereitstellt. Nach der Deklaration der Konstanten ist im Quelltext ein Anweisungsblock notiert, der für Konstanten einen eignen Scope repräsentiert. Es wird also für den Block eine neue lexikalische Umgebung erzeugt, wo die Bindung des zuvor erzeugten Objekts an einen lokalen Bezeichner vermerkt wird:
let EnvironmentRecord = {
// Reference outer lexical environment
[OuterEnvironment]: GlobalEnvironmentRecord,
// Binding for constant
b: {
value: '[[object reference]]',
immutable: true
}
};
Da innerhalb des Block Statements keine weiteren Anweisungen notiert sind, können die dazugehörigen Verwaltungsinformationen direkt danach wieder entsorgt oder auf die Seite gelegt werden. Sprich, die in dem Block erzeugte Konstante hört auf zu existieren. Die Konstante, die im globalen Scope deklariert wurde, lebt nach Verlassen des Blocks aber weiter und damit auch alle Verwaltungsinformationen, die mit dem referenzierten Objekt verknüpft sind.
Eine Konstante in JavaScript zeichnet sich im Wesentlichen dadurch aus, dass in der Verwaltungsstruktur für die Konstante ein Flag gesetzt wird, das signalisiert, dass kein anderer Wert an den Namen der Konstante gebunden werden darf. Es wird aber kein Flag auf dem Objekt gesetzt, das Änderungen an dem Objekt verhindert. Letzteres ist in JavaScript aber auch möglich, wobei verschiedene Varianten zu unterscheiden sind:
const object = Object.preventExtensions({});
Object.isExtensible(object); // False
// Raises type error in strict mode
object.property = 'value';
Mit der Methode preventExtensions
kann auf dem Objekt ein Flag gesetzt werden das verhindert, dass neue Eigenschaften auf dem Objekt definiert werden. Je nach dem ob das Programm im strikten oder im schlampigen Modus ausgeführt wird, scheitert die Definition einer Eigenschaft entweder still und leise oder es wird ein Typfehler geworfen. Das Flag verhindert aber nicht, dass bereits existierenden Objekteigenschaften neue Werte zugewiesen werden oder das Eigenschaften gelöscht werden.
const object = Object.seal({
property: 'value'
});
Object.isSealed(object); // true
// Raises type error in strict mode
delete object.property;
Mit der Methode seal
kann dafür gesorgt werden, dass die Eigenschaften eines Objekts nicht mehr konfiguriert werden können. Das heißt, es können keine Eigenschaften gelöscht werden und es können nur solchen Eigenschaften neue Werte zugewiesen werden, deren Attribut writable
vor der Versiegelung auf true
gesetzt war. Auch hier gilt, dass je nach Ausführungsmodus eine unerlaubte Aktion entweder wirkungslos bleibt oder zu einem Typfehler führt.
const object = Object.freeze({
property: 'value'
});
Object.isFrozen(object); // true
// Raises type error in strict mode
object.property = 'other value';
Schließlich können mit der Methode freeze
alle Eigenschaften eines Objekts auf read only gesetzt werden. Zudem können keine Eigenschaften gelöscht oder neu hinzugefügt werden und es kann auch kein neuer Prototyp für das Objekt bestimmt werden. Das Setzen dieses Flags führt aber nicht dazu, dass rekursiv alle Eigenschaften des Objekts abgeklappert werden und das Flag auch auf allen Objekten gesetzt wird, auf die eine Eigenschaft verweist. Objekte als Eigenschaftswert können weiterhin verändert werden. So wie bei den anderen oben beschriebenen Methoden sind die Wirkungen immer auf das Objekt begrenzt, für das die jeweilige Methode aufgerufen wurde.
Viele Grüße,
Orlok
„Dance like it hurts.
Make love like you need money.
Work when people are watching.“ — Dogbert