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.
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.
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
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
) .
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
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) .
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.
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);
}
}
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.
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.
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:.
// 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;
}
}
<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 "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)
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
})();