90 votes

Comment éviter les variables globales en JavaScript ?

Nous savons tous que variables globales sont tout sauf une bonne pratique. Mais il y a plusieurs cas où il est difficile de coder sans elles. Quelles techniques utilisez-vous pour éviter l'utilisation de variables globales ?

Par exemple, dans le scénario suivant, comment n'utiliseriez-vous pas une variable globale ?

Code JavaScript :

var uploadCount = 0;

window.onload = function() {
    var frm = document.forms[0];

    frm.target = "postMe";
    frm.onsubmit = function() {
        startUpload();
        return false;
    }
}

function startUpload() {
    var fil = document.getElementById("FileUpload" + uploadCount);

    if (!fil || fil.value.length == 0) {
        alert("Finished!");
        document.forms[0].reset();
        return;
    }

    disableAllFileInputs();
    fil.disabled = false;
    alert("Uploading file " + uploadCount);
    document.forms[0].submit();
}

Balisage pertinent :

<iframe src="test.htm" name="postHere" id="postHere"
  onload="uploadCount++; if(uploadCount > 1) startUpload();"></iframe>

<!-- MUST use inline JavaScript here for onload event
     to fire after each form submission. -->

Ce code provient d'un formulaire web avec plusieurs <input type="file"> . Il télécharge les fichiers un par un pour éviter les grosses demandes. Pour ce faire, il POST dans l'iframe, attend la réponse qui déclenche le onload de l'iframe, puis déclenche la soumission suivante.

Vous n'êtes pas obligé de répondre à cet exemple de manière spécifique, je le donne simplement à titre de référence pour une situation dans laquelle je suis incapable de trouver un moyen d'éviter les variables globales.

3 votes

Utiliser l'expression de fonction immédiatement invoquée (IIFE) Vous pouvez en savoir plus ici : codearsenal.net/2014/11/

76voto

eyelidlessness Points 28034

Le moyen le plus simple est d'envelopper votre code dans une fermeture et de n'exposer manuellement à la portée globale que les variables dont vous avez besoin globalement :

(function() {
    // Your code here

    // Expose to global
    window['varName'] = varName;
})();

Pour répondre au commentaire de Crescent Fresh : pour supprimer complètement les variables globales du scénario, le développeur devrait modifier un certain nombre de choses supposées dans la question. Cela ressemblerait beaucoup plus à ceci :

Javascript :

(function() {
    var addEvent = function(element, type, method) {
        if('addEventListener' in element) {
            element.addEventListener(type, method, false);
        } else if('attachEvent' in element) {
            element.attachEvent('on' + type, method);

        // If addEventListener and attachEvent are both unavailable,
        // use inline events. This should never happen.
        } else if('on' + type in element) {
            // If a previous inline event exists, preserve it. This isn't
            // tested, it may eat your baby
            var oldMethod = element['on' + type],
                newMethod = function(e) {
                    oldMethod(e);
                    newMethod(e);
                };
        } else {
            element['on' + type] = method;
        }
    },
        uploadCount = 0,
        startUpload = function() {
            var fil = document.getElementById("FileUpload" + uploadCount);

            if(!fil || fil.value.length == 0) {    
                alert("Finished!");
                document.forms[0].reset();
                return;
            }

            disableAllFileInputs();
            fil.disabled = false;
            alert("Uploading file " + uploadCount);
            document.forms[0].submit();
        };

    addEvent(window, 'load', function() {
        var frm = document.forms[0];

        frm.target = "postMe";
        addEvent(frm, 'submit', function() {
            startUpload();
            return false;
        });
    });

    var iframe = document.getElementById('postHere');
    addEvent(iframe, 'load', function() {
        uploadCount++;
        if(uploadCount > 1) {
            startUpload();
        }
    });

})();

HTML :

<iframe src="test.htm" name="postHere" id="postHere"></iframe>

Tu ne le fais pas. besoin de un gestionnaire d'événement en ligne sur le <iframe> il se déclenchera quand même à chaque chargement avec ce code.

Concernant l'événement de chargement

Voici un cas d'essai démontrant qu'il n'est pas nécessaire d'utiliser une ligne de commande. onload événement. Cela dépend de la référence à un fichier (/emptypage.php) sur le même serveur, sinon vous devriez pouvoir simplement coller ceci dans une page et l'exécuter.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>untitled</title>
</head>
<body>
    <script type="text/javascript" charset="utf-8">
        (function() {
            var addEvent = function(element, type, method) {
                if('addEventListener' in element) {
                    element.addEventListener(type, method, false);
                } else if('attachEvent' in element) {
                    element.attachEvent('on' + type, method);

                    // If addEventListener and attachEvent are both unavailable,
                    // use inline events. This should never happen.
                } else if('on' + type in element) {
                    // If a previous inline event exists, preserve it. This isn't
                    // tested, it may eat your baby
                    var oldMethod = element['on' + type],
                    newMethod = function(e) {
                        oldMethod(e);
                        newMethod(e);
                    };
                } else {
                    element['on' + type] = method;
                }
            };

            // Work around IE 6/7 bug where form submission targets
            // a new window instead of the iframe. SO suggestion here:
            // http://stackoverflow.com/q/875650
            var iframe;
            try {
                iframe = document.createElement('<iframe name="postHere">');
            } catch (e) {
                iframe = document.createElement('iframe');
                iframe.name = 'postHere';
            }

            iframe.name = 'postHere';
            iframe.id = 'postHere';
            iframe.src = '/emptypage.php';
            addEvent(iframe, 'load', function() {
                alert('iframe load');
            });

            document.body.appendChild(iframe);

            var form = document.createElement('form');
            form.target = 'postHere';
            form.action = '/emptypage.php';
            var submit = document.createElement('input');
            submit.type = 'submit';
            submit.value = 'Submit';

            form.appendChild(submit);

            document.body.appendChild(form);
        })();
    </script>
</body>
</html>

L'alerte se déclenche chaque fois que je clique sur le bouton d'envoi dans Safari, Firefox, IE 6, 7 et 8.

0 votes

Ou fournir une sorte d'accesseur. Je suis d'accord.

3 votes

Il est utile, lorsque les gens votent contre des choses, qu'ils expliquent leur raisonnement.

7 votes

Je n'ai pas voté contre. Cependant, dire window['varName'] = varName revient à faire une déclaration globale de var en dehors de la fermeture. var foo = "bar"; (function() { alert(window['foo']) })();

60voto

erenon Points 9361

Je suggère que le modèle de module .

YAHOO.myProject.myModule = function () {

    //"private" variables:
    var myPrivateVar = "I can be accessed only from within YAHOO.myProject.myModule.";

    //"private" method:
    var myPrivateMethod = function () {
        YAHOO.log("I can be accessed only from within YAHOO.myProject.myModule");
    }

    return  {
        myPublicProperty: "I'm accessible as YAHOO.myProject.myModule.myPublicProperty."
        myPublicMethod: function () {
            YAHOO.log("I'm accessible as YAHOO.myProject.myModule.myPublicMethod.");

            //Within myProject, I can access "private" vars and methods:
            YAHOO.log(myPrivateVar);
            YAHOO.log(myPrivateMethod());

            //The native scope of myPublicMethod is myProject; we can
            //access public members using "this":
            YAHOO.log(this.myPublicProperty);
        }
    };

}(); // the parens here cause the anonymous function to execute and return

3 votes

Je vais faire +1 parce que je comprends cela et que c'est extrêmement utile, mais je ne sais toujours pas comment cela serait efficace dans la situation où je n'utilise qu'une seule variable globale. Corrigez-moi si je me trompe, mais l'exécution de cette fonction et son retour entraînent le stockage de l'objet retourné dans YAHOO.myProject.myModule qui est une variable globale. N'est-ce pas ?

11 votes

@Josh : la variable globale n'est pas mauvaise. Les variables globales_S_ sont mauvaises. Gardez le nombre de variables globales aussi bas que possible.

0 votes

Toute la fonction anonyme serait exécutée chaque fois que vous voudriez accéder à l'une des propriétés/méthodes publiques des "modules", n'est-ce pas ?

7voto

Justin Johnson Points 16243

Tout d'abord, il est impossible d'éviter le JavaScript global, il y aura toujours quelque chose qui se balancera dans la portée globale. Même si vous créez un espace de nom, ce qui est toujours une bonne idée, cet espace de nom sera global.

Il existe toutefois de nombreuses approches permettant de ne pas abuser de la portée globale. Deux des plus simples consistent à utiliser la fermeture ou, puisque vous n'avez qu'une seule variable dont vous devez assurer le suivi, à la définir comme une propriété de la fonction elle-même (qui peut alors être traitée comme une fonction static variable).

Fermeture

var startUpload = (function() {
  var uploadCount = 1;  // <----
  return function() {
    var fil = document.getElementById("FileUpload" + uploadCount++);  // <----

    if(!fil || fil.value.length == 0) {    
      alert("Finished!");
      document.forms[0].reset();
      uploadCount = 1; // <----
      return;
    }

    disableAllFileInputs();
    fil.disabled = false;
    alert("Uploading file " + uploadCount);
    document.forms[0].submit();
  };
})();

* Notez que l'incrémentation de uploadCount se passe ici en interne

Propriété fonctionnelle

var startUpload = function() {
  startUpload.uploadCount = startUpload.count || 1; // <----
  var fil = document.getElementById("FileUpload" + startUpload.count++);

  if(!fil || fil.value.length == 0) {    
    alert("Finished!");
    document.forms[0].reset();
    startUpload.count = 1; // <----
    return;
  }

  disableAllFileInputs();
  fil.disabled = false;
  alert("Uploading file " + startUpload.count);
  document.forms[0].submit();
};

Je ne suis pas sûr de savoir pourquoi uploadCount++; if(uploadCount > 1) ... est nécessaire, car il semble que la condition sera toujours vraie. Mais si vous avez besoin d'un accès global à la variable, alors l'option propriété de la fonction que j'ai décrite ci-dessus vous permettra de le faire sans que la variable soit réellement globale.

<iframe src="test.htm" name="postHere" id="postHere"
  onload="startUpload.count++; if (startUpload.count > 1) startUpload();"></iframe>

Cependant, si c'est le cas, vous devriez probablement utiliser un objet littéral ou un objet instancié et procéder de la manière OO normale (où vous pouvez utiliser le modèle de module si vous le souhaitez).

2 votes

Vous pouvez absolument éviter la portée globale. Dans votre exemple de "fermeture", il suffit de supprimer "var startUpload = '" du début, et cette fonction sera complètement fermée, sans aucune accessibilité au niveau global. En pratique, beaucoup de gens préfèrent exposer une seule variable, dans laquelle sont contenues les références à tout le reste

1 votes

@derrylwc Dans ce cas, startUpload est la variable unique à laquelle vous faites référence. Suppression de var startUpload = de l'exemple de fermeture signifie que la fonction interne ne pourra jamais être exécutée car il n'y a pas de référence à celle-ci. La question d'éviter la pollution de la portée globale concerne la variable interne du compteur, uploadCount qui est utilisé par startUpload . De plus, je pense que le PO essaie d'éviter de polluer toute portée en dehors de cette méthode avec l'utilisation interne de la fonction uploadCount variable.

0 votes

Si le code à l'intérieur de la fermeture anonyme ajoute l'uploader en tant qu'écouteur d'événement, il sera bien sûr "capable d'être exécuté" lorsque l'événement approprié se produira.

6voto

Nosredna Points 33670

Il est parfois utile d'avoir des variables globales en JavaScript. Mais ne les laissez pas pendre directement de la fenêtre comme ça.

Au lieu de cela, créez un seul objet "espace de nom" pour contenir vos globaux. Pour des points bonus, mettez-y tout, y compris vos méthodes.

0 votes

Comment puis-je faire cela ? Créer un seul objet d'espace de nom pour contenir mes globaux ?

4voto

ChaosPandion Points 37025
window.onload = function() {
  var frm = document.forms[0];
  frm.target = "postMe";
  frm.onsubmit = function() {
    frm.onsubmit = null;
    var uploader = new LazyFileUploader();
    uploader.startUpload();
    return false;
  }
}

function LazyFileUploader() {
    var uploadCount = 0;
    var total = 10;
    var prefix = "FileUpload";	
    var upload = function() {
    	var fil = document.getElementById(prefix + uploadCount);

    	if(!fil || fil.value.length == 0) {    
    		alert("Finished!");
    		document.forms[0].reset();
    		return;
    	 }

    	disableAllFileInputs();
    	fil.disabled = false;
    	alert("Uploading file " + uploadCount);
    	document.forms[0].submit();
    	uploadCount++;

    	if (uploadCount < total) {
    		setTimeout(function() {
    			upload();
    		}, 100); 
    	}
    }

    this.startUpload = function() {
    	setTimeout(function() {
    		upload();
    	}, 100);  
    } 		
}

0 votes

Comment puis-je incrémenter l'uploadCount à l'intérieur de mes onload sur l'iframe ? C'est crucial.

1 votes

OK, je vois ce que tu as fait ici. Malheureusement, ce n'est pas la même chose. Cela déclenche tous les téléchargements séparément, mais en même temps (techniquement, il y a 100 ms entre eux). La solution actuelle les télécharge de manière séquentielle, ce qui signifie que le deuxième téléchargement ne commence pas avant que le premier ne soit terminé. C'est pourquoi la fonction inline onload est nécessaire. L'affectation programmatique du gestionnaire ne fonctionne pas, car il ne se déclenche que la première fois. Le gestionnaire en ligne se déclenche à chaque fois (pour une raison quelconque).

0 votes

Je vais quand même faire +1, car je vois que c'est une méthode efficace pour cacher une variable globale.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X