web-dev-qa-db-fra.com

Comment éviter les variables globales en JavaScript?

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

Par exemple, étant donné le scénario suivant, comment ne pas utiliser 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 demandes énormes. Il le fait en POST ing dans l'iframe, en attendant la réponse qui déclenche la charge de l'iframe, puis déclenche la soumission suivante.

Vous n'êtes pas obligé de répondre spécifiquement à cet exemple, je ne fais que le fournir comme référence à une situation dans laquelle je ne peux pas penser à un moyen d'éviter les variables globales.

75
Josh Stodola

Le moyen le plus simple consiste à encapsuler votre code dans une fermeture et à exposer manuellement uniquement les variables dont vous avez besoin globalement à la portée globale:

(function() {
    // Your code here

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

Pour répondre au commentaire de Crescent Fresh: afin de supprimer complètement les variables globales du scénario, le développeur devra 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>

Vous n'avez pas besoin un gestionnaire d'événements en ligne sur le <iframe>, il se déclenchera toujours sur chaque chargement avec ce code.

Concernant l'événement de chargement

Voici un cas de test démontrant que vous n'avez pas besoin d'un événement onload en ligne. Cela dépend du référencement d'un fichier (/emptypage.php) sur le même serveur, sinon vous devriez pouvoir simplement le coller 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 Envoyer dans Safari, Firefox, IE 6, 7 et 8.

64
eyelidlessness

Je suggère 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
56
erenon

Tout d'abord, il est impossible d'éviter JavaScript global, quelque chose va toujours balancer la portée globale. Même si vous créez un espace de noms, ce qui est toujours une bonne idée, cet espace de noms sera global.

Il existe cependant de nombreuses approches pour ne pas abuser de la portée mondiale. Deux des plus simples sont soit d'utiliser la fermeture, soit comme vous n'avez qu'une seule variable dont vous devez garder la trace, définissez-la simplement comme une propriété de la fonction elle-même (qui peut ensuite être traitée comme une variable static) .

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 produit en interne ici

Propriété de fonction

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 sais pas 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, la méthode propriété de 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 tel est le cas, vous devriez probablement utiliser un objet littéral ou un objet instancié et procéder à ce sujet de la manière normale OO (où vous pouvez utiliser le modèle de module s'il vous plaît) .

7
Justin Johnson

Parfois, il est logique d'avoir des variables globales en JavaScript. Mais ne les laissez pas pendre directement de la fenêtre comme ça.

À la place, créez un seul objet "namespace" pour contenir vos globales. Pour les points bonus, mettez-y tout, y compris vos méthodes.

4
Nosredna
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);  
    }       
}
4
ChaosPandion

Pour ce faire, vous pouvez également créer un objet, puis lui ajouter des méthodes.

var object = {
  a = 21,
  b = 51
};

object.displayA = function() {
 console.log(object.a);
};

object.displayB = function() {
 console.log(object.b);
};

De cette façon, seul l'objet 'obj' est exposé et des méthodes y sont attachées. Cela revient à l'ajouter dans l'espace de noms.

2
Ajay Poshak

Certaines choses vont être dans l'espace de noms global - à savoir, quelle que soit la fonction que vous appelez à partir de votre code JavaScript en ligne.

En général, la solution est de tout emballer dans une fermeture:

(function() {
    var uploadCount = 0;
    function startupload() {  ...  }
    document.getElementById('postHere').onload = function() {
        uploadCount ++;
        if (uploadCount > 1) startUpload();
    };
})();

et évitez le gestionnaire en ligne.

1
Jimmy

L'utilisation de fermetures peut être acceptable pour les petits et moyens projets. Cependant, pour les grands projets, vous souhaiterez peut-être diviser votre code en modules et les enregistrer dans différents fichiers.

J'ai donc écrit plugin jQuery Secret pour résoudre le problème.

Dans votre cas avec ce plugin, le code ressemblerait à ceci:.

JavaScript:

// Initialize uploadCount.
$.secret( 'in', 'uploadCount', 0 ).

// Store function disableAllFileInputs.
secret( 'in', 'disableAllFileInputs', function(){
  // Code for 'disable all file inputs' goes here.

// Store function startUpload
}).secret( 'in', 'startUpload', function(){
    // 'this' points to the private object in $.secret
    // where stores all the variables and functions
    // ex. uploadCount, disableAllFileInputs, startUpload.

    var fil = document.getElementById( 'FileUpload' + uploadCount);

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

    // Use the stored disableAllFileInputs function
    // or you can use $.secret( 'call', 'disableAllFileInputs' );
    // it's the same thing.
    this.disableAllFileInputs();
    fil.disabled = false;

    // this.uploadCount is equal to $.secret( 'out', 'uploadCount' );
    alert( 'Uploading file ' + this.uploadCount );
    document.forms[0].submit();

// Store function iframeOnload
}).secret( 'in', 'iframeOnload', function(){
    this.uploadCount++;
    if( this.uploadCount > 1 ) this.startUpload();
});

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

    frm.target = "postMe";
    frm.onsubmit = function() {
        // Call out startUpload function onsubmit
        $.secret( 'call', 'startUpload' );
        return false;
    }
}

Balisage pertinent:

<iframe src="test.htm" name="postHere" id="postHere" onload="$.secret( 'call', 'iframeOnload' );"></iframe>

Ouvrez votre Firebug , vous ne trouverez pas de globaux du tout, pas même la fonction :)

Pour une documentation complète, veuillez consulter ici .

Pour une page de démonstration, veuillez voir this .

Code source sur GitHub .

1
ben

Pour "sécuriser" les variables globales induviduelles:

function gInitUploadCount() {
    var uploadCount = 0;

    gGetUploadCount = function () {
        return uploadCount; 
    }
    gAddUploadCount= function () {
        uploadCount +=1;
    } 
}

gInitUploadCount();
gAddUploadCount();

console.log("Upload counter = "+gGetUploadCount());

Je suis un novice de JS, j'utilise actuellement cela dans un projet. (j'apprécie tout commentaire et critique)

0
Koenees

Utilisez des fermetures. Quelque chose comme ça vous donne une portée autre que globale.

(function() {
    // Your code here
    var var1;
    function f1() {
        if(var1){...}
    }

    window.var_name = something; //<- if you have to have global var
    window.glob_func = function(){...} //<- ...or global function
})();
0
NilColor