Tu ne peux pas faire ça non plus, rentre chez toi et cultive
Au fait, parlons de la façon d'utiliser la suppression
Il y a quelques semaines, j'ai eu la chance de consulter le livre Javascript orienté objet de Stoyan Stefanov. Le livre a des notes élevées sur Amazon (12 critiques, 5 étoiles), j'étais donc curieux de voir s'il s'agissait d'un tel livre. livre recommandable, j'ai commencé à lire le chapitre sur les fonctions. J'ai beaucoup aimé la façon dont ce livre expliquait les choses, et les exemples étaient organisés de manière très agréable et progressive, à première vue, il semblait que même un débutant serait capable de maîtriser cela. Cependant, presque immédiatement, j'ai découvert un malentendu intéressant qui s'est produit tout au long du chapitre : la suppression des fonctions de fonction. Il y avait également quelques autres erreurs (telles que les déclarations de fonction et les expressions de fonction), mais nous n'en discuterons pas pour l'instant.
Ce livre revendique :
"Une fonction est traitée comme une variable normale - elle peut être copiée dans une variable différente, ou même supprimée. " Cette explication est suivie d'un exemple :
En ignorant certains points-virgules manquants, pouvez-vous voir où se trouve l'erreur dans ces lignes de code ? De toute évidence, l'erreur est que la suppression de la variable sum ne réussira pas. L'expression de suppression ne doit pas renvoyer true, et le type de somme ne doit pas renvoyer. "indéfini" non plus. Tout cela parce que la suppression de variables n'est pas possible en JavaScript, du moins pas de cette manière de déclaration.
Alors, que se passe-t-il exactement dans cet exemple ? Est-ce un bug ? Ou est-ce une utilisation particulière ? Ce code est en fait la sortie réelle de la console Firebug, et Stoyan a dû l'utiliser comme outil ? test rapide. C'est presque comme si Firebug obéissait à d'autres règles de suppression. C'est Firebug qui a égaré Stoyan. Alors, que se passe-t-il ici ?
Avant de répondre à cette question, nous devons d'abord comprendre comment fonctionne l'opérateur de suppression en JavaScript : qu'est-ce qui peut être supprimé et qu'est-ce qui ne peut pas être supprimé ? Aujourd'hui, je vais essayer d'expliquer cette question en détail. Nous examinerons celle de Firebug. comportement "bizarre" et réalisez que ce n'est pas si bizarre après tout. Nous examinerons de plus près ce qui se passe dans les coulisses de la déclaration des variables, des fonctions, de l'attribution de valeurs aux propriétés et de leur suppression. la compatibilité du navigateur et certains des bugs les plus notoires. Nous discuterons également du mode strict d'ES5 et de la manière dont il modifie le comportement de l'opérateur de suppression.
J'utiliserai JavaScript et ECMAScript de manière interchangeable, ils signifient tous deux ECMAScript (à moins de parler évidemment de l'implémentation JavaScript de Mozilla)Sans surprise, les explications sur la suppression sont assez rares sur le web. L'article du MDC est probablement la meilleure ressource pour comprendre, mais, malheureusement, il manque quelques détails intéressants sur le sujet. Bizarre Oui, l'une des choses oubliées est responsable. pour le comportement étrange de Firebug. Et la référence MSDN est presque inutile dans ces aspects
.
ThéorieAlors, pourquoi peut-on supprimer les propriétés d'un objet :
Remarque : Lorsqu'un attribut ne peut pas être supprimé, l'opérateur de suppression ne renverra que false
Pour comprendre cela, nous devons d'abord saisir ces concepts sur les instances de variables et les propriétés de propriété - des concepts qui, malheureusement, sont rarement mentionnés dans les livres JavaScript. Je vais essayer de les expliquer dans les prochains paragraphes. Une brève revue de ces concepts. . Ces concepts sont difficiles à comprendre ! Si vous ne vous souciez pas de « pourquoi ces choses fonctionnent comme elles le font », n'hésitez pas à sauter ce chapitre
.Type de code :
Dans ECMAScript, il existe 3 types différents de code exécutable : le code global, le code de fonction et le code d'évaluation. Ces types ont un nom plus ou moins explicite, voici un bref aperçu :
.Lorsqu'un morceau de code source est considéré comme un programme, il sera exécuté dans l'environnement global et est considéré comme du code global. Dans un environnement de navigateur, le contenu des éléments de script est généralement interprété comme un programme et donc exécuté comme global. code.
Tout code exécuté directement dans une fonction est évidemment considéré comme du code de fonction. Dans les navigateurs, le contenu des attributs d'événement (tels que
) est généralement interprété en code de fonction.
Enfin, le texte du code appliqué à la fonction intégrée eval est interprété comme du code Eval. Nous découvrirons bientôt pourquoi ce type est spécial.
Contexte d'exécution :
Lorsque le code ECMAScript est exécuté, il se produit généralement dans un contexte d'exécution spécifique. Le contexte d'exécution est un concept d'entité quelque peu abstrait qui permet de comprendre le fonctionnement de la portée (Scope) et de l'instanciation variable (Instanciation variable). code exécutable, il existe un contexte d'exécution qui lui correspond. Lorsqu'une fonction est exécutée, on dit que « le contrôle du programme entre dans le contexte d'exécution du code de la fonction » ; lorsqu'un morceau de code global est exécuté, le contrôle du programme entre dans le contexte d'exécution ; de code global, etc.
Comme vous pouvez le voir, les contextes d'exécution peuvent logiquement former une pile. Premièrement, il peut y avoir un morceau de code global avec son propre contexte d'exécution, puis ce morceau de code peut appeler une fonction, l'emportant avec lui. . Cette fonction peut appeler une autre fonction, etc. Même si la fonction est appelée de manière récursive, elle entrera dans un nouveau contexte d'exécution à chaque appel
.Objet d'activation/Objet variable :
Chaque contexte d'exécution est associé à un objet dit variable. Semblable au contexte d'exécution, l'objet variable est une entité abstraite, un mécanisme utilisé pour décrire les instances de variables. le code source est généralement ajouté à cet objet variable en tant que propriétés.
Lorsque le contrôle du programme entre dans le contexte d'exécution du code global, un objet global est utilisé comme objet variable. C'est pourquoi les variables de fonction déclarées comme globales deviennent des propriétés d'objet globales.
var foo = 1;
GLOBAL_OBJECT.foo; // 1
foo === GLOBAL_OBJECT.foo; // vrai
barre de fonctions(){}
typeof GLOBAL_OBJECT.bar; // "fonction"
GLOBAL_OBJECT.bar === barre; // vrai
D'accord, donc les variables globales deviennent des propriétés de l'objet global, mais qu'arrive-t-il aux variables locales (celles définies dans le code de fonction) ? Elles se comportent en fait de manière très similaire : elles deviennent des objets variables (objet Variable). dans le code de fonction, un objet variable n'est pas un objet global, mais un objet dit d'activation sera appelé à chaque fois qu'il entrera dans le contexte d'exécution du code de fonction.
Non seulement les variables et fonctions déclarées dans le code de la fonction deviendront des propriétés de l'objet actif ; cela sera également fait dans chaque paramètre de fonction (correspondant au nom du paramètre formel correspondant) et dans un objet Arguments spécial (avec des arguments comme nom). Notez que l'objet actif est un mécanisme de description interne et n'est pas accessible à partir du code du programme.
Attributs de la propriété
Nous y sommes presque. Maintenant que nous avons une idée claire de ce qui arrive aux variables (elles deviennent des propriétés), le seul concept qui reste à comprendre est celui des attributs de propriété. Chaque propriété peut avoir zéro ou plusieurs attributs, choisis parmi. l'ensemble suivant : ReadOnly, DontEnum, DontDelete et Internal. Vous pouvez les considérer comme des indicateurs - un attribut peut ou non être présent dans l'attribut. Pour nous Pour la discussion d'aujourd'hui, nous ne nous intéressons qu'à DontDelete.
Lorsque les variables et fonctions déclarées deviennent des attributs de l'objet variable (ou de l'objet actif du code de fonction, ou de l'objet global du code global), ces attributs sont créés avec l'attribut DontDelete Cependant, toutes les propriétés explicites créées. par (ou implicite) l'affectation de propriété n'aura pas l'attribut DontDelete. C'est pourquoi nous pouvons supprimer certaines propriétés, mais pas d'autres
.Objets intégrés et DontDelete
Donc, c'est tout à propos de cela (DontDelete) : une caractéristique spéciale de l'attribut, utilisée pour contrôler si cet attribut peut être supprimé. Notez que certains attributs d'objet intégrés sont spécifiés pour contenir DontDelete, ils ne peuvent donc pas être supprimés. . Par exemple La variable d'arguments spéciaux (ou, comme nous le savons maintenant, une propriété d'un objet actif) a DontDelete. La propriété length d'une instance de fonction a également une propriété DontDelete.
L'attribut correspondant au paramètre de fonction possède également l'attribut DontDelete depuis sa création, nous ne pouvons donc pas le supprimer.
Cession non déclarée :
Vous vous souvenez peut-être qu'une affectation non déclarée crée une propriété sur l'objet global, à moins que la propriété ne se trouve déjà ailleurs dans la chaîne de portée avant l'objet global. Et nous connaissons maintenant l'affectation de propriété et les variables. La différence entre les déclarations - la. ce dernier définira la propriété DontDelete, mais pas le premier. Nous devons comprendre pourquoi une affectation non déclarée crée une propriété supprimable.
Veuillez noter : les propriétés sont déterminées lors de la création de la propriété et les affectations ultérieures ne modifieront pas les propriétés des propriétés existantes.
Que s'est-il passé dans Firebug ? Pourquoi les variables déclarées dans la console peuvent-elles être supprimées ? Cela ne viole-t-il pas ce que nous avons appris auparavant, le code Eval sera confronté à un comportement spécial lors de la déclaration des variables ? Les variables déclarées dans Eval sont en fait créées en tant que propriétés sans l'attribut DontDelete.
foo; // 1
supprimer foo ; // vrai
typeof foo; // "non défini"
Supprimer les variables via Eval :
Ce comportement d'évaluation intéressant, couplé à un autre aspect d'ECMAScript, pourrait techniquement nous permettre de supprimer les attributs "non supprimables". Le problème avec les déclarations de fonction est qu'elles peuvent remplacer des variables portant le même nom dans le même contexte d'exécution.
Remarquez comment la déclaration de fonction est prioritaire et remplace la variable du même nom (ou, en d'autres termes, la même propriété dans l'objet variable). En effet, la déclaration de fonction est instanciée après la déclaration de variable et est autorisée). Remplacez-les (déclarations de variables). Une déclaration de fonction remplace non seulement la valeur d'une propriété, elle remplace également les attributs de cette propriété. Si nous déclarons une fonction via eval, cette fonction doit la remplacer par ses propres attributs de l'original. (remplacée). De plus, puisqu'une variable déclarée via eval crée une propriété sans l'attribut DontDelete, l'instanciation de cette nouvelle fonction supprimera en fait l'attribut DontDelete existant de la propriété, ce qui permettra à une propriété d'être supprimée (et, évidemment, de pointer sa propriété). valeur à la fonction nouvellement créée).
Malheureusement, cette « triche » ne fonctionne dans aucune implémentation actuelle. Peut-être qu'il me manque quelque chose ici, ou que le comportement est si obscur que les implémenteurs ne l'ont pas remarqué.
Compatibilité des navigateurs :
Comprendre comment les choses fonctionnent en théorie est utile, mais la pratique est ce qui compte le plus. Les navigateurs suivent-ils les normes en matière de création/suppression de variables/propriétés ? La réponse est : dans la plupart des cas, oui.
J'ai écrit un ensemble de tests simple pour tester la compatibilité du navigateur avec l'opérateur de suppression, y compris des tests sous le code global, le code de fonction et le code Eval. L'ensemble de tests a vérifié la valeur de retour et la valeur d'attribut de l'opérateur de suppression si (comme ils le devraient). ) être réellement supprimé. La valeur de retour de delete n'est pas aussi importante que son résultat réel. Peu importe si delete renvoie true au lieu de false, ce qui compte, c'est que ceux qui ont les attributs de l'attribut DontDelete ne soient pas supprimés. et vice versa.
Les navigateurs modernes sont généralement tout à fait compatibles. À l'exception de la fonctionnalité d'évaluation que j'ai mentionnée précédemment, les navigateurs suivants ont réussi tous les tests : Opera 7.54, Firefox 1.0, Safari 3.1.2, Chrome 4.
Safari 2.x et 3.0.4 ont des problèmes pour supprimer les paramètres de fonction ; ces propriétés semblent être créées sans DontDelete, elles peuvent donc être supprimées. Safari 2.x a plus de problèmes - supprimer des variables de type non-références (par exemple : supprimer. 1) lèvera une exception ; les déclarations de fonction créeront des propriétés supprimables (mais, étrangement, les déclarations de variables ne le seront pas) ; les déclarations de variables dans eval deviendront non supprimables (mais les déclarations de fonction ne seront pas supprimables).
Semblable à Safari, Konqueror (3.5, pas 4.3) lèvera une exception lors de la suppression de types non-référence (tels que : delete 1) et rendra par erreur les variables de fonction supprimables.
Note du traducteur :
J'ai testé les dernières versions de Chrome, Firefox et IE, et j'ai essentiellement conservé le pass sauf les 23 et 24, qui ont échoué. J'ai également testé UC et certains navigateurs mobiles, en plus du navigateur intégré du Nokia E72. À l'exception des échecs 15 et 16, la plupart des autres navigateurs intégrés ont le même effet que les navigateurs de bureau. Mais il convient de mentionner que le navigateur intégré du Blackberry Curve 8310/8900 peut réussir le test 23, ce qui m'a surpris.
Bogue Gecko DontDelete :
Les navigateurs Gecko 1.8.x - Firefox 2.x, Camino 1.x, Seamonkey 1.x, etc. - présentent un bug très intéressant, l'affectation explicite à une propriété supprimera son attribut DontDelete, même si l'attribut est créé via une déclaration de variable ou une déclaration de fonction.
Étonnamment, Internet Explorer 5.5 - 8 réussit l'ensemble des tests, sauf que la suppression des types non références (par exemple : supprimer 1) génère une exception (tout comme l'ancien Safari). Mais sous IE, il y a des bugs plus sérieux, c'est). pas si évident. Ces bugs sont liés à l'objet Global.
Bogues IE :
Tout ce chapitre concerne les bugs d'Internet Explorer ? Wow !
Dans IE (au moins IE 6-8), l'expression suivante lèvera une exception (lorsqu'elle est exécutée dans du code global) :
this.x = 1;
delete x ; // TypeError : l'objet ne prend pas en charge cette action
Celui-ci le fait aussi, mais lève une exception différente, ce qui rend les choses plus intéressantes :
var x = 1;
supprimer this.x ; // TypeError : Impossible de supprimer 'this.x'
Il semble que dans IE, les déclarations de variables dans le code global ne créent pas de propriétés sur l'objet global. Créer une propriété via une affectation (this.x = 1), puis la supprimer via delete x générera une erreur en déclarant créer la propriété. (var x = 1) puis supprimez-le en supprimant this.x génère une autre erreur.
Mais ce n'est pas tout. La création d'une propriété via une affectation explicite entraînera en fait la levée d'une exception lors de la suppression. Non seulement il y a une erreur ici, mais la propriété en cours de création semble avoir l'attribut DontDelete, ce qui n'est bien sûr pas le cas. J'aurais dû.
this.x = 1;
supprimer this.x ; // TypeError : l'objet ne prend pas en charge cette action
typeof x; // "numéro" (existe toujours, n'a pas été supprimé comme il aurait dû l'être !)
supprimer x ; // TypeError : l'objet ne prend pas en charge cette action
typeof x; // "numéro" (n'a pas été supprimé à nouveau)
Maintenant, nous supposerions que sous IE, les affectations non déclarées (qui devraient créer des propriétés sur l'objet global) créent effectivement des propriétés supprimables
x = 1;
supprimer x ; // vrai
type de x ; // "non défini"
Cependant, si vous supprimez cet attribut via cette référence dans le code global (supprimez this.x), une erreur similaire apparaîtra.
x = 1;
supprimer this.x ; // TypeError : Impossible de supprimer 'this.x'
Si nous voulons généraliser ce comportement, il semble que la suppression d'une variable du code global à l'aide de delete this.x ne réussit jamais. Lorsque la propriété en question est créée via une affectation explicite (this.x = 1), delete renvoie une erreur lorsque la propriété est créée. est créé par affectation non déclarée (x = 1) ou par déclaration (var x = 1), delete renvoie une autre erreur.
delete x, en revanche, ne devrait générer une erreur que si la propriété est créée par affectation explicite - this.x = 1. Si une propriété est créée par déclaration (var x = 1), la suppression ne se produit jamais, et la suppression renvoie correctement false Si une propriété a été créée via une affectation non déclarée (x = 1), la suppression fonctionne comme prévu.
J'ai repensé à ce problème en septembre, et Garrett Smith a suggéré que sous IE,
"L'objet variable globale est implémenté en tant qu'objet JScript et l'objet global est implémenté par l'hôte".
Garrett a utilisé l'entrée de blog d'Eric Lippert comme référence.
Nous pouvons plus ou moins confirmer cette théorie en implémentant quelques tests. Notez que this et window semblent pointer vers le même objet (si l'on peut faire confiance à l'opérateur ===), mais l'objet variable (déclaration de fonction L'objet où il se trouve) est différent de celui indiqué par ceci.
getBase() === this.getBase(); // false
this.getBase() === this.getBase(); // true
window.getBase() === this.getBase(); // true
window.getBase() === getBase(); // false
Incompréhension :
La beauté de comprendre pourquoi les choses fonctionnent comme elles le font ne peut être sous-estimée. J'ai vu des idées fausses sur l'opérateur de suppression sur le Web. Par exemple, cette réponse sur Stackoverflow (avec une note étonnamment élevée), a expliqué Confidence <.>
"Lorsque l'opérande cible n'est pas une propriété d'objet, la suppression ne doit pas être effectuée".Maintenant que nous comprenons le cœur du comportement de l'opération de suppression, l'erreur de cette réponse devient évidente. delete ne fait pas de distinction entre les variables et les propriétés (en fait, pour la suppression, ce sont deux types de référence) et ne se soucie en réalité que de DontDelete. attribut (et si l'attribut lui-même existe).
Il est également très intéressant de voir les différentes idées fausses contrées les unes par les autres, dans le même fil de discussion, une personne a d'abord suggéré de simplement supprimer la variable (ce qui n'aurait aucun effet à moins qu'elle ne soit déclarée dans une eval), tandis qu'une autre a fourni une correction de bug. expliquant comment delete peut être utilisé pour supprimer des variables dans le code global, mais pas dans le code de fonction
.
Soyez extrêmement prudent avec l'interprétation de JavaScript sur Internet, l'approche idéale est de toujours comprendre la nature du problème ;).
supprimer et objet hôte :L'algorithme de suppression est à peu près le suivant :
Si l'opérande n'est pas un type référence, renvoie true
Si l'objet n'a pas de propriété directe portant ce nom, renvoie vrai (comme nous le savons, l'objet peut être un objet actif ou un objet global)
Si l'attribut existe mais possède l'attribut DontDelete, renvoyez false
Dans d'autres cas, supprimez l'attribut et renvoyez true
Cependant, le comportement de l'opérateur delete sur les objets hôtes est imprévisible. Et il n'y a en réalité rien de mal à ce comportement : (selon la norme), les objets hôtes sont autorisés à exécuter des fonctions telles que la lecture ([[Get]] interne), l'écriture. (méthode interne [[Put]]) et delete (méthode interne [[Delete]]), plusieurs opérateurs implémentent n'importe quel comportement. Cette grâce pour le comportement personnalisé [[Delete]] est ce qui change l'objet hôte en Cause de confusion.
type de fenêtre.alerte; // "fonction"
La morale de cette histoire est la suivante : ne faites jamais confiance à l'objet hôte.
Mode strict ES5 :
Alors, que nous apporte le mode strict ECMAScript 5 ? Il introduit très peu de restrictions. Des erreurs de syntaxe se produisent lorsque l'expression de l'opérateur delete est une référence directe à une variable, un paramètre de fonction ou un identifiant de fonction. , si la propriété a l'attribut interne [[Configurable]] == false, une erreur de type sera générée.
De plus, la suppression de variables non déclarées (ou de références non résolues) générera également une erreur de syntaxe :
"utiliser strict";
supprimer i_dont_exist ; // SyntaxError
Une affectation non déclarée se comporte de la même manière qu'une variable non déclarée en mode strict (sauf que cette fois, elle génère une erreur de référence au lieu d'une erreur de syntaxe) :
"utiliser strict";
i_dont_exist = 1; // ReferenceError
Comme vous pouvez maintenant le comprendre, toutes les restrictions ont plus ou moins de sens, car la suppression de variables, de déclarations de fonctions et de paramètres provoque beaucoup de confusion. Plutôt que d'ignorer silencieusement les suppressions, le mode strict prend des mesures plus agressives et plus descriptives.
Résumé :
Ce billet de blog a fini par être assez long, donc je ne vais pas parler de choses comme la suppression d'un objet tableau avec delete ou de ce que cela signifie. Vous pouvez vous référer à l'article MDC pour une explication dédiée (ou lire la norme. et Faites vos propres expériences).
Voici un bref résumé du fonctionnement de la suppression en JavaScript :
Les déclarations de variables et de fonctions sont des propriétés d'objets actifs ou d'objets globaux
Les attributs ont certaines caractéristiques, parmi lesquelles DontDelete est la caractéristique qui détermine si l'attribut peut être supprimé
.
Les déclarations de variables et de fonctions dans le code global ou fonctionnel créent toujours des propriétés avec l'attribut DontDelete.
Les paramètres de fonction sont toujours des propriétés de l'objet actif et ont DontDelete.
Les variables et fonctions déclarées dans le code Eval sont toujours créées sans propriétés DontDelete
.
Les nouvelles propriétés n'ont aucun attribut lors de leur création (et bien sûr pas de DontDelete
).
L'objet hôte est autorisé à décider comment réagir à l'opération de suppression.
Si vous souhaitez vous familiariser davantage avec ce qui est décrit ici, veuillez vous référer à la spécification ECMA-262 3e édition.
J'espère que vous avez apprécié cet article et appris quelque chose de nouveau. Toutes les questions, suggestions ou corrections sont les bienvenues.