Das kannst du auch nicht machen, nach Hause gehen und auf dem Bauernhof arbeiten
Lassen Sie uns übrigens über die Verwendung von „Löschen“ sprechen
Vor ein paar Wochen hatte ich die Gelegenheit, Stoyan Stefanovs Buch Object-Oriented Javascript auszuprobieren. Das Buch hat bei Amazon hohe Bewertungen (12 Rezensionen, 5 Sterne), daher war ich neugierig Ein empfehlenswertes Buch. Mir hat die Art und Weise, wie dieses Buch die Dinge erklärt, sehr gut gefallen. Auf den ersten Blick sah es so aus, als ob auch ein Anfänger damit zurechtkommen würde Allerdings entdeckte ich fast sofort ein interessantes Missverständnis, das sich im gesamten Kapitel wiederholte – das Löschen von Funktionsfunktionen. Es gab auch einige andere Fehler (z. B. Funktionsdeklarationen und Funktionsausdrücke), auf die wir jedoch vorerst nicht eingehen .
Dieses Buch behauptet:
„Eine Funktion wird wie eine normale Variable behandelt – sie kann in eine andere Variable kopiert oder sogar gelöscht werden.“ Im Anschluss an diese Erklärung ist ein Beispiel angehängt:
Ignorieren Sie einige fehlende Semikolons. Können Sie sehen, wo der Fehler in diesen Codezeilen liegt? Der Fehler besteht offensichtlich darin, dass das Löschen der Summenvariablen nicht erfolgreich sein sollte. Der Löschausdruck sollte nicht „true“ zurückgeben Auch „undefiniert“. Alles nur, weil das Löschen von Variablen in JavaScript nicht möglich ist.
Was genau passiert in diesem Beispiel? Oder handelt es sich um eine spezielle Verwendung? Wahrscheinlich nicht für die eigentliche Ausgabe in der Firebug-Konsole Schnelles Testen. Es ist fast so, als ob Firebug einige andere Löschregeln befolgt. Also, was ist hier los?
Bevor wir diese Frage beantworten, müssen wir zunächst verstehen, wie der Löschoperator in JavaScript funktioniert: Was kann gelöscht werden und was nicht? Heute werde ich versuchen, diese Frage im Detail zu erklären „Seltsames“ Verhalten und stellen fest, dass es doch nicht so seltsam ist Die Kompatibilität des Browsers und einige der berüchtigtsten Fehler besprechen wir auch den strikten Modus von ES5 und wie er das Verhalten des Löschoperators ändert.Ich werde JavaScript und ECMAScript austauschbar verwenden, beide bedeuten ECMAScript (es sei denn, es geht offensichtlich um Mozillas JavaScript-Implementierung)
Es überrascht nicht, dass es im Internet kaum Erklärungen zum Löschen gibt. Der MDC-Artikel ist wahrscheinlich die beste Quelle zum Verständnis, aber leider fehlen einige interessante Details zu diesem Thema. Ja, einer der vergessenen Dinge ist dafür verantwortlich für das seltsame Verhalten von Firebug. Und die MSDN-Referenz ist in dieser Hinsicht fast nutzlos
Theorie
Warum können wir also die Eigenschaften eines Objekts löschen:
Hinweis: Wenn ein Attribut nicht gelöscht werden kann, gibt der Löschoperator nur false zurück
Um dies zu verstehen, müssen wir zunächst diese Konzepte zu Variableninstanzen und Eigenschaftseigenschaften verstehen – Konzepte, die in JavaScript-Büchern leider selten erwähnt werden. Ich werde versuchen, sie in den nächsten Absätzen kurz zu erklären Konzepte sind schwer zu verstehen! Wenn es Ihnen egal ist, warum diese Dinge so funktionieren, können Sie dieses Kapitel gerne überspringen
Codetyp:
In ECMAScript gibt es drei verschiedene Arten von ausführbarem Code: Globaler Code, Funktionscode und Eval-Code. Diese Typen sind sich im Namen mehr oder weniger selbsterklärend, hier ein kurzer Überblick:
Wenn ein Quellcode als Programm betrachtet wird, wird er in der globalen Umgebung ausgeführt und gilt als globaler Code. In einer Browserumgebung wird der Inhalt von Skriptelementen normalerweise als Programm interpretiert und daher als global ausgeführt Code.
Jeder Code, der direkt in einer Funktion ausgeführt wird, wird offensichtlich als Funktionscode betrachtet. In Browsern wird der Inhalt von Ereignisattributen (z. B.
) normalerweise in Funktionscode interpretiert.
Schließlich wird der auf die integrierte Funktion eval angewendete Codetext als Eval-Code interpretiert. Bald werden wir herausfinden, warum dieser Typ etwas Besonderes ist.
Ausführungskontext:
Wenn ECMAScript-Code ausgeführt wird, erfolgt er normalerweise in einem bestimmten Ausführungskontext. Der Ausführungskontext ist ein etwas abstraktes Entitätskonzept, das hilft, zu verstehen, wie der Bereich (Scope) und die Variableninstanziierung (Variableninstanziierung) funktionieren Ausführbarer Code, es gibt einen entsprechenden Ausführungskontext. Wenn eine Funktion ausgeführt wird, sagen wir: „Die Programmsteuerung tritt in den Ausführungskontext des Funktionscodes ein.“ Wenn ein Teil des globalen Codes ausgeführt wird, tritt die Programmsteuerung in den Ausführungskontext ein von globalem Code usw.
Wie Sie sehen, können Ausführungskontexte logischerweise einen Stapel bilden. Zuerst kann es einen Teil des globalen Codes mit seinem eigenen Ausführungskontext geben, und dann könnte dieser Teil des Codes eine Funktion aufrufen und ihn mitnehmen . Diese Funktion kann eine andere Funktion usw. aufrufen. Auch wenn die Funktion rekursiv aufgerufen wird, tritt sie bei jedem Aufruf in einen neuen Ausführungskontext ein
Aktivierungsobjekt/Variablenobjekt:Jedem Ausführungskontext ist ein sogenanntes Variablenobjekt zugeordnet. Das Variablenobjekt ist eine abstrakte Entität, ein Mechanismus zur Beschreibung von Variableninstanzen Quellcode wird diesem Variablenobjekt normalerweise als Eigenschaften hinzugefügt
Wenn die Programmsteuerung in den Ausführungskontext von globalem Code eintritt, wird ein globales Objekt als Variablenobjekt verwendet. Aus diesem Grund werden als global deklarierte Funktionsvariablen zu globalen Objekteigenschaften.
GLOBAL_OBJECT.foo; // 1
foo === GLOBAL_OBJECT.foo; // true
typeof GLOBAL_OBJECT.bar; // "function"
GLOBAL_OBJECT.bar === bar; // true
Nicht nur die im Funktionscode deklarierten Variablen und Funktionen werden zu Eigenschaften des aktiven Objekts; dies geschieht auch in jedem Funktionsparameter (entsprechend dem Namen des entsprechenden formalen Parameters) und einem speziellen Arguments-Objekt (mit Argumenten). als Name). Beachten Sie, dass das aktive Objekt ein interner Beschreibungsmechanismus ist und nicht über den Programmcode aufgerufen werden kann
Da wir nun eine klare Vorstellung davon haben, was mit Variablen passiert (sie werden zu Eigenschaften), müssen wir nur noch die Eigenschaftsattribute verstehen. Jede Eigenschaft kann null oder mehr Attribute haben, aus denen ausgewählt werden kann der folgende Satz: ReadOnly, DontEnum, DontDelete und Internal Sie können sie sich als Flags vorstellen – ein Attribut kann im Attribut vorhanden sein oder auch nicht. Für uns interessiert uns für die heutige Diskussion nur DontDelete.
Wenn die deklarierten Variablen und Funktionen zu Attributen des Variablenobjekts (oder des aktiven Objekts des Funktionscodes oder des globalen Objekts des globalen Codes) werden, werden diese Attribute mit dem DontDelete-Attribut erstellt. Allerdings werden alle expliziten Eigenschaften erstellt Durch (oder implizite) Eigenschaftszuweisung wird das DontDelete-Attribut nicht vorhanden sein. Aus diesem Grund können wir einige Eigenschaften löschen, andere jedoch nicht
Das ist es also (DontDelete): ein spezielles Attribut des Attributs, mit dem gesteuert wird, ob dieses Attribut gelöscht werden kann. Beachten Sie, dass einige integrierte Objektattribute so angegeben sind, dass sie DontDelete enthalten, sodass sie nicht gelöscht werden können Zum Beispiel hat die Variable „Sonderargumente“ (oder, wie wir jetzt wissen, eine Eigenschaft eines aktiven Objekts) die Eigenschaft „Länge“ einer Funktionsinstanz
Das dem Funktionsparameter entsprechende Attribut verfügt seit seiner Erstellung auch über das DontDelete-Attribut, sodass wir es nicht löschen können.
Nicht angemeldete Abtretung:
Sie erinnern sich vielleicht, dass eine nicht deklarierte Zuweisung eine Eigenschaft für das globale Objekt erstellt, es sei denn, die Eigenschaft befindet sich bereits an anderer Stelle in der Bereichskette vor dem globalen Objekt. Und jetzt wissen wir über Eigenschaftszuweisungen und Variablen Bescheid Letzteres legt die DontDelete-Eigenschaft fest, Ersteres jedoch nicht. Wir müssen verstehen, warum eine nicht deklarierte Zuweisung eine löschbare Eigenschaft erstellt.
Bitte beachten Sie: Eigenschaften werden bei der Erstellung der Eigenschaft festgelegt und nachfolgende Zuweisungen ändern nicht die Eigenschaften vorhandener Eigenschaften. Es ist wichtig, diesen Unterschied zu verstehen.
Was ist in Firebug passiert? Warum können in der Konsole deklarierte Variablen gelöscht werden? Nun, wie ich bereits sagte, wird es beim Deklarieren von Variablen ein besonderes Verhalten geben? In Eval deklarierte Variablen werden tatsächlich als Eigenschaften ohne das DontDelete-Attribut erstellt.
foo; // 1
delete foo; // true
typeof foo; // „undefiniert“
Variablen über Eval löschen:
Dieses interessante Auswertungsverhalten, gepaart mit einem anderen Aspekt von ECMAScript, könnte es uns technisch ermöglichen, „nicht löschbare“ Attribute zu entfernen. Die Sache mit Funktionsdeklarationen ist, dass sie Variablen mit demselben Namen im selben Ausführungskontext überschreiben können.
Beachten Sie, dass die Funktionsdeklaration Vorrang hat und die gleichnamige Variable (oder mit anderen Worten die gleiche Eigenschaft im Variablenobjekt) überschreibt. Dies liegt daran, dass die Funktionsdeklaration nach der Variablendeklaration instanziiert wird und zulässig ist Überschreiben Sie sie (Variablendeklarationen). Eine Funktionsdeklaration ersetzt nicht nur den Wert einer Eigenschaft, sondern auch die Attribute dieser Eigenschaft. Wenn wir eine Funktion über eval deklarieren, sollte diese Funktion sie durch ihre eigenen Attribute des Originals ersetzen (ersetzte) Eigenschaft. Da über eval deklarierte Variablen auch Eigenschaften ohne das DontDelete-Attribut erstellen, wird durch die Instanziierung dieser neuen Funktion tatsächlich das vorhandene DontDelete-Attribut aus dem Attribut entfernt, sodass eine Eigenschaft gelöscht werden kann (und natürlich auf ihren Wert verweist). die neu erstellte Funktion).
Leider funktioniert dieser „Cheat“ in keiner aktuellen Implementierung. Vielleicht übersehe ich hier etwas, oder das Verhalten ist einfach so unklar, dass es den Implementierern nicht aufgefallen ist.
Browserkompatibilität:
Es ist nützlich zu verstehen, wie die Dinge in der Theorie funktionieren, aber die Praxis ist am wichtigsten. Halten sich Browser an Standards, wenn es um die Erstellung/Löschung von Variablen/Eigenschaften geht? Die Antwort lautet: In den meisten Fällen Ja.
Ich habe einen einfachen Testsatz geschrieben, um die Kompatibilität des Browsers mit dem Löschoperator zu testen, einschließlich Tests unter globalem Code, Funktionscode und Eval-Code. Der Testsatz überprüfte den Rückgabewert und den Attributwert des Löschoperators, ob (wie sie sollten). ) wirklich gelöscht werden, ist nicht so wichtig wie das tatsächliche Ergebnis. Es spielt keine Rolle, ob delete „true“ statt „false“ zurückgibt. Wichtig ist, dass diejenigen, die über die Attribute „DontDelete“ verfügen, nicht gelöscht werden. und umgekehrt.
Moderne Browser sind im Allgemeinen recht kompatibel. Mit Ausnahme der zuvor erwähnten Evaluierungsfunktion haben die folgenden Browser alle Testsätze bestanden: Opera 7.54, Firefox 1.0, Safari 3.1.2, Chrome 4.
Safari 2.x und 3.0.4 haben Probleme beim Löschen von Funktionsparametern; diese Eigenschaften scheinen ohne DontDelete erstellt zu werden, daher können sie bei Safari 2.x mehr Probleme haben – beim Löschen von Nicht-Referenztypvariablen (z. B. delete 1) wird eine Ausnahme auslösen; Funktionsdeklarationen werden löschbare Eigenschaften erstellen (aber seltsamerweise werden Variablendeklarationen in eval nicht löschbar) (Funktionsdeklarationen sind jedoch nicht löschbar).
Ähnlich wie Safari löst Konqueror (3.5, nicht 4.3) beim Löschen von Nicht-Referenztypen (z. B. „löschen 1“) eine Ausnahme aus und macht Funktionsvariablen versehentlich löschbar.
Anmerkung des Übersetzers:
Ich habe die neuesten Versionen von Chrome, Firefox und IE getestet und im Grunde den Pass beibehalten, mit Ausnahme von 23 und 24, die fehlgeschlagen sind. Zusätzlich zum integrierten Browser des Nokia E72 habe ich auch UC und einige mobile Browser getestet. Mit Ausnahme von Fail 15 und 16 haben die meisten anderen integrierten Browser den gleichen Effekt wie Desktop-Browser. Erwähnenswert ist jedoch, dass der integrierte Browser des Blackberry Curve 8310/8900 Test 23 bestehen kann, was mich überrascht hat.
Gecko DontDelete Fehler:
Gecko 1.8.x-Browser – Firefox 2.x, Camino 1.x, Seamonkey 1.x usw. – weisen einen sehr interessanten Fehler auf: Durch die explizite Zuweisung zu einer Eigenschaft wird deren DontDelete-Attribut gelöscht, selbst wenn das Attribut erstellt wird durch eine Variablendeklaration oder Funktionsdeklaration.
Überraschenderweise hat Internet Explorer 5.5 - 8 den gesamten Testsatz bestanden, mit der Ausnahme, dass das Löschen von Nicht-Referenztypen (z. B. Löschen 1) eine Ausnahme auslösen würde (genau wie beim alten Safari). Unter IE gibt es jedoch schwerwiegendere Fehler nicht so offensichtlich. Diese Fehler hängen mit dem globalen Objekt zusammen.
IE-Fehler:
In diesem ganzen Kapitel geht es um Internet Explorer-Fehler? Wow!
Im IE (mindestens IE 6-8) löst der folgende Ausdruck eine Ausnahme aus (bei Ausführung im globalen Code):
this.x = 1;
delete x; // TypeError: Objekt unterstützt diese Aktion nicht
Dies gilt auch, löst jedoch eine andere Ausnahme aus, was die Sache interessanter macht:
var x = 1;
delete this.x; // TypeError: „This.x“ kann nicht gelöscht werden
Es sieht so aus, als ob Variablendeklarationen im globalen Code keine Eigenschaften für das globale Objekt erstellen und das anschließende Löschen über „delete x“ einen Fehler auslöst (var x = 1) und löschen Sie es dann durch delete this.x löst einen weiteren Fehler aus.
Aber das ist noch nicht alles. Das Erstellen einer Eigenschaft per expliziter Zuweisung führt nicht nur dazu, dass beim Löschen ein Fehler auftritt, sondern die erstellte Eigenschaft scheint auch das DontDelete-Attribut zu haben, was natürlich nicht der Fall ist Hätte es tun sollen.
this.x = 1;
delete this.x; // TypeError: Objekt unterstützt diese Aktion nicht
typeof x; // „number“ (existiert noch, wurde nicht gelöscht, wie es hätte sein sollen!)
delete x; // TypeError: Objekt unterstützt diese Aktion nicht
typeof x; // „number“ (wurde nicht wieder gelöscht)
Nun gehen wir davon aus, dass nicht deklarierte Zuweisungen (die Eigenschaften für das globale Objekt erstellen sollten) tatsächlich löschbare Eigenschaften erstellen
x = 1;
delete x; // true
typeof x; // „undefiniert“
Wenn Sie dieses Attribut jedoch über diesen Verweis im globalen Code löschen (delete this.x), wird ein ähnlicher Fehler angezeigt.
x = 1;
delete this.x; // TypeError: „This.x“ kann nicht gelöscht werden
Wenn wir dieses Verhalten verallgemeinern wollen, scheint das Löschen einer Variablen aus dem globalen Code mit „delete this.x“ nie erfolgreich zu sein. Wenn die betreffende Eigenschaft durch explizite Zuweisung erstellt wird (this.x = 1), löst „delete“ einen Fehler aus wird durch nicht deklarierte Zuweisung (x = 1) oder durch Deklaration (var x = 1) erstellt, delete löst einen weiteren Fehler aus.
delete und das Löschen gibt korrekterweise „false“ zurück. Wenn eine Eigenschaft über eine nicht deklarierte Zuweisung erstellt wurde (x = 1), funktioniert das Löschen wie erwartet
Ich habe im September noch einmal über dieses Thema nachgedacht, und Garrett Smith hat das unter IE vorgeschlagen,„Das globale Variablenobjekt wird als JScript-Objekt implementiert, und das globale Objekt wird vom Host implementiert.“
Garrett nutzte den Blogeintrag von Eric Lippert als Referenz.
Wir können diese Theorie mehr oder weniger bestätigen, indem wir einige Tests durchführen. Beachten Sie, dass this und window auf dasselbe Objekt zu verweisen scheinen (sofern wir dem ===-Operator vertrauen können), aber das variable Objekt (Funktionsdeklaration Das Objekt wo es befindet) unterscheidet sich von dem, auf den hier verwiesen wird.
window.getBase() === this.getBase(); // true
window.getBase() === getBase(); // false
Missverständnis:
Das Schöne daran, zu verstehen, warum Dinge so funktionieren, wie sie funktionieren, ist nicht zu unterschätzen. Ich habe im Internet einige Missverständnisse über den Löschoperator gesehen, zum Beispiel diese Antwort auf Stackoverflow (mit einer überraschend hohen Bewertung), erklärt
„Wenn der Zieloperand keine Objekteigenschaft ist, sollte das Löschen ein No-Op sein.“
Da wir nun den Kern des Verhaltens von Löschvorgängen verstehen, wird der Fehler dieser Antwort deutlich. delete unterscheidet nicht zwischen Variablen und Eigenschaften (tatsächlich handelt es sich bei delete um Referenztypen) und kümmert sich eigentlich nur um DontDelete Attribut (und ob das Attribut selbst existiert).
Es ist auch sehr interessant zu sehen, wie die verschiedenen Missverständnisse gegenseitig bekämpft werden. Im selben Thread schlug eine Person zunächst vor, einfach die Variable zu löschen (was keine Auswirkungen hätte, wenn sie nicht in einer Auswertung deklariert würde), während eine andere Person eine Fehlerkorrektur vorsah Erläutern, wie mit delete Variablen im globalen Code gelöscht werden können, jedoch nicht im Funktionscode
Seien Sie äußerst vorsichtig bei der Interpretation von JavaScript im Internet. Der ideale Ansatz besteht darin, immer die Natur des Problems zu verstehen ;)
Objekt löschen und hosten:
Der Löschalgorithmus ist ungefähr wie folgt:
Wenn der Operand kein Referenztyp ist, geben Sie true zurück
Wenn das Objekt keine direkte Eigenschaft mit diesem Namen hat, wird true zurückgegeben (wie wir wissen, kann das Objekt ein aktives Objekt oder ein globales Objekt sein)
Wenn die Eigenschaft vorhanden ist, aber über das DontDelete-Attribut verfügt, geben Sie false
zurück
In anderen Fällen löschen Sie das Attribut und geben true
zurück
Allerdings ist das Verhalten des Löschoperators bei Hostobjekten unvorhersehbar. Und an diesem Verhalten ist eigentlich nichts auszusetzen: (gemäß dem Standard) dürfen Hostobjekte Funktionen wie Lesen (intern [[Get]]) und Schreiben ausführen (interne [[Put]]-Methode) und löschen (interne [[Delete]]-Methode) mehrere Operatoren implementieren ein beliebiges Verhalten. Diese Gnade für benutzerdefiniertes [[Delete]]-Verhalten ist es, was das Hostobjekt in Ursache für Verwirrung ändert.
Wir haben einige IE-Macken gesehen, bei denen das Löschen eines bestimmten Objekts (offensichtlich als Hostobjekt implementiert) einen Fehler auslöst. Einige Versionen von Firefox geben beim Löschen von window.location einen Fehler aus. Sie können dem nicht vertrauen Rückgabewert von delete, wenn das Objekt ein Hostobjekt ist. Sehen wir uns an, was in Firefox passiert:
window.alert löschen; // true
typeof window.alert; // „function“
Das Löschen von window.alert gibt „true“ zurück, auch wenn es absolut keinen Grund gibt, warum diese Eigenschaft ein solches Ergebnis verursachen sollte. Sie wird in eine Referenz aufgelöst (also wird sie im ersten Schritt nicht „true“ zurückgeben). des Fensterobjekts (es wird also im zweiten Schritt nicht „true“ zurückgeben). Das einzige Mal, dass das Löschen „true“ zurückgibt, besteht darin, den vierten Schritt zu erreichen und dieses Attribut tatsächlich zu löschen
Die Moral dieser Geschichte lautet: Vertraue niemals dem Wirtsobjekt.Strikter ES5-Modus:
Was bringt uns der strikte Modus ECMAScript 5? Es treten nur sehr wenige Einschränkungen auf, wenn der Ausdruck des Löschoperators eine direkte Referenz auf eine Variable, einen Funktionsparameter oder einen Funktionsbezeichner ist , wenn die Eigenschaft das interne Attribut [[Configurable]] == false hat, wird ein Typfehler ausgegeben.
Außerdem führt das Löschen nicht deklarierter Variablen (oder nicht aufgelöster Referenzen) zu einem Syntaxfehler:
"strikt verwenden";
delete i_dont_exist; // SyntaxError
Eine nicht deklarierte Zuweisung verhält sich ähnlich wie eine nicht deklarierte Variable im strikten Modus (außer dass sie dieses Mal einen Referenzfehler anstelle eines Syntaxfehlers auslöst):
"strikt verwenden";
i_dont_exist = 1; // ReferenceError
Wie Sie jetzt verstehen können, sind alle Einschränkungen mehr oder weniger sinnvoll, da das Löschen von Variablen, Funktionsdeklarationen und Parametern so viel Verwirrung stiftet. Anstatt Löschungen stillschweigend zu ignorieren, werden im strikten Modus aggressivere und beschreibendere Maßnahmen ergriffen.
Zusammenfassung:
Dieser Blog-Beitrag ist ziemlich lang geworden, daher werde ich nicht auf Dinge wie das Löschen von Array-Objekten mit „Delete“ oder deren Bedeutung eingehen. Eine entsprechende Erklärung finden Sie im MDC-Artikel (oder lesen Sie den Standard und). Machen Sie Ihre eigenen Experimente).
Hier ist eine kurze Zusammenfassung, wie das Löschen in JavaScript funktioniert:
Variablen- und Funktionsdeklarationen sind Eigenschaften aktiver Objekte oder globaler Objekte
Attribute haben einige Merkmale, darunter DontDelete, das bestimmt, ob das Attribut gelöscht werden kann
Variablen- und Funktionsdeklarationen im globalen oder funktionalen Code erstellen immer Eigenschaften mit dem DontDelete-Attribut.
Funktionsparameter sind immer Eigenschaften des aktiven Objekts und haben DontDelete.
Im Eval-Code deklarierte Variablen und Funktionen werden immer ohne DontDelete-Eigenschaften
erstellt
Neue Eigenschaften haben beim Erstellen keine Attribute (und natürlich kein DontDelete).
Das Host-Objekt darf entscheiden, wie es auf den Löschvorgang reagiert.
Wenn Sie sich mit den hier beschriebenen Inhalten vertraut machen möchten, lesen Sie bitte die ECMA-262-Spezifikation der 3. Ausgabe.