En JavaScript, nous devrions utiliser autant que possible des variables locales au lieu de variables globales. Tout le monde connaît cette phrase, mais qui l'a dit en premier ? Pourquoi faire ça ? Y a-t-il une base pour cela ? Si vous ne le faites pas, quelle perte cela entraînera-t-il en termes de performances ? Cet article explorera les réponses à ces questions et comprendra fondamentalement quels facteurs sont liés aux performances en lecture et en écriture des variables.
【Original】 Performances des variables JavaScript
【Auteur】Nicholas C. Zakas
[Français] En JavaScript, pourquoi devrions-nous utiliser des variables locales autant que possible?
[Traducteur] Mingda
Ce qui suit est une traduction du texte original :
Sur la question de savoir comment améliorer les performances de JavaScript, la suggestion la plus couramment entendue est d'utiliser des variables locales au lieu de variables globales. C'est un conseil qui m'est resté et qui ne l'a jamais remis en question au cours de mes neuf années de travail dans le développement Web, et il est basé sur la gestion de la méthode de cadrage et de résolution d'identifiant (résolution d'identifiant) par JavaScript.
Tout d'abord, nous devons préciser que les fonctions sont incarnées sous forme d'objets en JavaScript. Le processus de création d'une fonction est en fait le processus de création d'un objet. Chaque objet fonction possède une propriété interne appelée [[Scope]], qui contient les informations de portée lors de la création de la fonction. En fait, l'attribut [[Scope]] correspond à une liste d'objets (Variable Objects), et les objets de la liste sont accessibles depuis l'intérieur de la fonction. Par exemple, si nous créons une fonction globale A, alors la propriété interne [[Scope]] de A ne contient qu'un seul objet global (Global Object), et si nous créons une nouvelle fonction B dans A, alors l'attribut [[Scope] ] de B contient deux objets, l'objet Activation Object de la fonction A est à l'avant et l'objet global (Global Object) est à l'arrière.
Lorsqu'une fonction est exécutée, un objet exécutable (Execution Object) est automatiquement créé et lié à une chaîne de portée (Scope Chain). La chaîne de portée sera établie à travers les deux étapes suivantes pour la résolution des identifiants.
1. Tout d'abord, copiez les objets dans les propriétés internes de l'objet fonction [[Scope]] dans la chaîne de portée dans l'ordre.
2. Deuxièmement, lorsque la fonction est exécutée, un nouvel objet Objet d'activation sera créé. Cet objet contient les définitions de celui-ci, les paramètres (arguments) et les variables locales (y compris les paramètres nommés). Cet objet Objet d'activation sera placé en action. . L'avant de la chaîne de domaines.
Lors de l'exécution du code JavaScript, lorsqu'un identifiant est rencontré, il sera recherché dans la chaîne de portée du contexte d'exécution (Execution Context) en fonction du nom de l'identifiant. En commençant par le premier objet de la chaîne de portée (l'objet d'activation de la fonction), s'il n'est pas trouvé, recherchez l'objet suivant dans la chaîne de portée, et ainsi de suite, jusqu'à ce que la définition de l'identifiant soit trouvée. Si le dernier objet de la portée, qui est l'objet global, n'est pas trouvé après la recherche, une erreur sera générée, indiquant à l'utilisateur que la variable n'est pas définie. Il s'agit du processus de modèle d'exécution de fonction et de résolution d'identifiant (Identifier Resolution) décrit dans la norme ECMA-262. Il s'avère que la plupart des moteurs JavaScript sont effectivement implémentés de cette manière. Il convient de noter que l'ECMA-262 n'impose pas l'utilisation de cette structure, mais décrit uniquement cette partie de la fonction.
Après avoir compris le processus de résolution des identifiants (Identifier Resolution), nous pouvons comprendre pourquoi les variables locales sont résolues plus rapidement que les variables d'autres portées, principalement parce que le processus de recherche est considérablement raccourci. Mais à quel point sera-t-il plus rapide ? Pour répondre à cette question, j'ai simulé une série de tests pour tester les performances des variables à différentes profondeurs de portée.
Le premier test consiste à écrire la valeur la plus simple dans une variable (la valeur littérale 1 est utilisée ici. Le résultat est présenté dans la figure ci-dessous, qui est très intéressante :
Il n'est pas difficile de voir à partir des résultats que lorsque le processus d'analyse des identifiants nécessite une recherche approfondie, il y aura une perte de performances, et le degré de perte de performances augmentera avec l'augmentation de la profondeur de l'identifiant. Sans surprise, Internet Explorer a obtenu les pires résultats (mais pour être honnête, il y a eu quelques améliorations dans IE 8). Il convient de noter qu'il existe ici quelques exceptions, Google Chrome et la dernière version de minuit de WebKit ont un temps d'accès aux variables très stable et n'augmente pas avec l'augmentation de la profondeur de la portée. Bien sûr, cela doit être attribué aux moteurs JavaScript de nouvelle génération qu’ils utilisent, V8 et SquirrelFish. Ces moteurs effectuent des optimisations lors de l'exécution du code, et il est clair que ces optimisations rendent l'accès aux variables plus rapide que jamais. Opera a également bien fonctionné, étant beaucoup plus rapide que IE, Firefox et la version actuelle de Safari, mais plus lent que les navigateurs basés sur V8 et Squirrelfish. Les performances de Firefox 3.1 Beta 2 sont un peu inattendues. L'efficacité d'exécution des variables locales est très élevée, mais à mesure que le nombre de couches de portée augmente, l'efficacité est considérablement réduite. Il convient de noter que j'utilise ici les paramètres par défaut, ce qui signifie que Firefox n'a pas la fonction Trace activée.
Les résultats ci-dessus ont été obtenus en effectuant des opérations d'écriture sur des variables. En fait, j'étais curieux de savoir si la situation serait différente lors de la lecture de variables, j'ai donc fait le test suivant. Il a été constaté que la vitesse de lecture est légèrement plus rapide que la vitesse d'écriture, mais la tendance des changements de performances est cohérente.
Comme lors du test précédent, Internet Explorer et Firefox étaient toujours les plus lents, et Opera a montré des performances très accrocheuses. De même, Chrome et la dernière version de Webkit Midnight Edition ont montré des tendances de performances qui n'ont rien à voir avec la profondeur de la portée. Cela vaut la peine d'y prêter attention. Oui, le temps d'accès variable dans Firefox 3.1 Beta 2 présente encore un étrange saut en profondeur.
Au cours du test, j'ai découvert un phénomène intéressant, à savoir que Chrome subira des pertes de performances supplémentaires lors de l'accès aux variables globales. Le temps d'accès aux variables globales n'a rien à voir avec le niveau de portée, mais il sera 50% plus long que le temps d'accès aux variables locales de même niveau.
Quels enseignements ces deux tests peuvent-ils nous apporter ? La première est de vérifier l’ancien point de vue, qui consiste à utiliser autant que possible des variables locales. Dans tous les navigateurs, l'accès aux variables locales est plus rapide que l'accès aux variables dans plusieurs étendues, y compris les variables globales. Les points suivants devraient être l'expérience acquise grâce à ce test :
* Vérifiez soigneusement toutes les variables utilisées dans la fonction. S'il existe une variable qui n'est pas définie dans la portée actuelle et est utilisée plus d'une fois, nous devons alors enregistrer cette variable dans un fichier. variable locale, utilisez cette variable locale pour effectuer des opérations de lecture et d'écriture. Cela peut nous aider à réduire la profondeur de recherche des variables en dehors de la portée à 1. Ceci est particulièrement important pour les variables globales, car les variables globales sont toujours recherchées à la dernière position de la chaîne de portée.
* Évitez d'utiliser l'instruction with. Parce que cela modifiera la chaîne de portée du contexte d'exécution (Execution Context) et ajoutera un objet (Variable Object) au début. Cela signifie que lors de l'exécution de with, les variables locales réelles sont déplacées vers la deuxième position de la chaîne de portée, ce qui entraînera une perte de performances.
* Si vous êtes sûr qu'un morceau de code lèvera définitivement une exception, évitez d'utiliser try-catch, car la branche catch est traitée dans la même chaîne de portée que with. Cependant, il n'y a aucune perte de performances dans le code de la branche try, il est donc toujours recommandé d'utiliser try-catch pour détecter les erreurs imprévisibles.
Si vous souhaitez plus de discussions sur ce sujet, j'ai donné une petite conférence lors du Meetup JavaScript de Mountain View le mois dernier. Vous pouvez télécharger les diapositives sur SlideShare ou regarder la vidéo complète de la fête, qui commence vers les 11 minutes de mon discours.
Notes du traducteur :
Si vous avez des doutes en lisant cet article, je vous suggère de lire les deux articles suivants :
* "Modèle d'exécution de modèle d'objet JavaScript" écrit par Richie
* "ECMA-262 Troisième édition", examinez principalement le chapitre 10, qui est le contexte d'exécution. Les termes mentionnés dans cet article y sont expliqués en détail.
À la fin, Nicholas a mentionné un Meetup JavaScript de Mountain View. Le site Web Meetup est en fait un site Web d'organisation pour diverses activités du monde réel. Vous devez contourner le pare-feu pour y accéder. Vivre en Californie est vraiment une bénédiction. activités auxquelles participer. hehe.