Portée des variables Javascript

Après CSS, faisons un petit passage par Javascript. C’est Yoan qui m’a fait passé un lien intéressant d’Ariel Flesler

La portée des variables javascript

La portée des variables Javascript trompe souvent le développeur débutant. Les variables variables fonctionnent par bloc de code. Pour faire court un bloc c’est ce qui est délimité par des accolades ouvrantes et fermantes.

Par défaut on va regarder si une variable est présente dans le bloc de code par défaut, sinon on regarde dans le bloc parent, et ainsi de suite jusqu’au niveau global. Les variables du niveau global sont simplement des propriétés de l’object window.

On peut forcer une variable locale avec le mot clé var. On a alors une nouvelle variable qui n’est pas partagé avec les blocs parents. Elle restera toutefois partagée avec les blocs enfants si eux même ne redéclarent pas la variable avec le mot clef var.

L’impact sur les performances

Cette portée impose au moteur javascript de faire des calculs, qui peuvent prendre plus ou moins de temps. C’est cet impact sur les performances que Ariel Flester a testé.

Il a tenté d’imbriquer des déclarations de fonctions anonymes les unes dans les autres. Chaque fonction déclare une variable et la fonction la plus interne tente d’y accéder. Le temps total nécessaire à javascript varie alors suivant qu’on tente d’accéder à une variable dans un bloc proche ou éloigné. 

Différences suivant les navigateurs

Les résultats suivant les navigateurs sont assez clairs : sur Firefox 2 aller chercher une variable dans le bloc parent prend 300ms (sur 400.000 itérations), sauter 3 blocs prend donc 900ms. Entre 78ms (utiliser une variable locale) et 969ms (utiliser une variable globale, avec trois blocs imbriqués), la différence est flagrante.

Sur Microsoft Internet Explorer (6 et 7) et Safari 3 on remarque aussi une perte de performance progressive suivant qu’on utilise des variables locales, des variables d’un bloc proche ou des variables situé à plusieurs niveaux plus haut. Pour les deux, le test tourne deux fois plus rapidement avec une variable locale qu’avec une variable globale située trois blocs au dessus.

C’est Firefox 3 et Opera 9 qui ont tout bon. Là non seulement le code tourne plus rapidement avec des variables locale (30 à 40ms contre 60 à 95 pour Firefox 2, Safari 3 et MSIE) mais en plus l’imbrication des blocs n’a aucune ou presque aucune influence. On remarque le gros progrès de Firefox entre la version 2 et 3.

Quid ?

Ariel a tout de même passé son code de 2,5 secondes à 230 ms, simplement en évitant d’utiliser une variable globale. On est là sur un gain de performance majeur.

Son code est très spécifique, avec certainement beaucoup d’itérations et d’itération, et l’impact sur votre propose code ne sera probablement pas aussi important. Son test est par exemple fait sur 3 imbrications et 400.000 itérations, sans pour autant atteindre un gain de 100ms (sauf sur Firefox 2, où le gain s’approche d’une seconde).

L’important

Toutefois, rien qu’à cause de Firefox 2, les résultats ne peuvent être ignorés. La bonne pratique est aussi simple à mettre en oeuvre :

  • évitez les variables globales,
  • utilisez avec parcimonie du principe des closures
  • et n’hésitez pas à dupliquer des contenus dans des variables locales si vous comptez les utiliser fréquemment.

Le mot clef var est votre ami.

Publié par edaspet

Plus d'informations sur mon profil en ligne

5 réponses sur « Portée des variables Javascript »

  1. Il y avait des posts sur le blog des développeurs d’IE sur le sujet, c’est assez intéressant, et même surprenant dans certains cas: http://blogs.msdn.com/ie/archive/2006/08/28/728654.aspx , http://blogs.msdn.com/ie/archive/2006/11/16/ie-javascript-performance-recommendations-part-2-javascript-code-inefficiencies.aspx et http://blogs.msdn.com/ie/archive/2007/01/04/ie-jscript-performance-recommendations-part-3-javascript-code-inefficiencies.aspx

    Grosso modo: les variables locales, c’est encore mieux que ce qu’on croit.

  2. Pour être sûr de la portée des variables et expliciter les variables globales, j’utilise tout le temps var et j’utilise window.foo pour une variable globale.

    Sinon, j’ai fait tourner le bench chez moi et Webkit est impressionnant pour le moment. Si j’ai bien compris ce qu’on m’a expliqué en anglais, une des astuces consiste à déterminer (si possible) au moment du parsing (donc en statique) si une variable locale existe. Du coup, à l’exécution, ça évite de regarder inutilement dans le contexte local. Visiblement, ça casse quelques sites et ce n’est pas encore intégré au prochain moteur JS (Squirellfish qui devrait arriver bientôt). Donc ça risque de ne pas être aussi impressionnant au final, même si ce sera compensé par l’amélioration globale du moteur.

    FF3ß5
    global : average : 44ms
    local1 : average : 57.6ms
    local2 : average : 55.8ms
    local3 : average : 36.4ms

    Opera 9.27
    global : average : 79ms
    local1 : average : 78.4ms
    local2 : average : 79.2ms
    local3 : average : 74.8ms

    Opera 9.5
    global : average : 51.4ms
    local1 : average : 26.8ms
    local2 : average : 35.6ms
    local3 : average : 24ms

    Safari 3.1
    global : average : 90.8ms
    local1 : average : 54.8ms
    local2 : average : 44.8ms
    local3 : average : 34.4ms

    Webkit r33561
    global : average : 17.8ms
    local1 : average : 17.4ms
    local2 : average : 16.4ms
    local3 : average : 16ms

  3. Point bonus à qui trouve pourquoi dans Firefox 3 l’accès à une variable globale est plus rapide que l’accès à des variables locales au bloc parent alors qu’il doit *forcément* chercher dans les blocs parents avant de chercher dans l’objet global.

    Cette bizarrerie se retouve chez Ariel, chez Rik et chez moi, dans des proportions similaires.

    La seule explication que je vois c’est que la recherche de la variable elle-même n’est pas vraiment lente. C’est ce qu’il fait après avoir trouvé la variable qui peut être un peu plus lent dans le cas local2 ou local1 que dans le cas global. La question c’est : que fait-il comme traitement ou optimisation qui pourrait être significativement plus long ?

    Par exemple, sachant que remonter les contextes est couteux, il pourrait tenter d’optimiser les accès futurs et mettre une sorte de cache : soit une optimisation efficace mais qu’il ne fait que pour les variables globales, soit une tentative d’optimisation pour les autres cas mais qui ce révèle contre-performance dans notre bench.

    Toutes explications bienvenues

Les commentaires sont fermés.