How to avoid global variables in JavaScript?
Sometimes it makes sense to have global variables in JavaScript. But don't leave them hanging directly off window like that.
Instead, create a single "namespace" object to contain your globals. For bonus points, put everything in there, including your methods.
First off, it is impossible to avoid global JavaScript, something will always be dangling the global scope. Even if you create a namespace, which is still a good idea, that namespace will be global.
There are many approaches, however, to not abuse the global scope. Two of the simplest are to either use closure, or since you only have one variable you need to keep track of, just set it as a property of the function itself (which can then be treated as a static
variable).
Closure
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();
};
})();
* Note that incrementing of uploadCount
is happening internally here
Function Property
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();
};
I'm not sure why uploadCount++; if(uploadCount > 1) ...
is necessary, as it looks like the condition will always be true. But if you do need global access to the variable, then the function property method I described above will allow you to do so without the variable actually being global.
<iframe src="test.htm" name="postHere" id="postHere"
onload="startUpload.count++; if (startUpload.count > 1) startUpload();"></iframe>
However, if that's the case, then you should probably use an object literal or instantiated object and go about this in the normal OO way (where you can use the module pattern if it strikes your fancy).
The easiest way is to wrap your code in a closure and manually expose only those variables you need globally to the global scope:
(function() {
// Your code here
// Expose to global
window['varName'] = varName;
})();
To address Crescent Fresh's comment: in order to remove global variables from the scenario entirely, the developer would need to change a number of things assumed in the question. It would look a lot more like this:
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>
You don't need an inline event handler on the <iframe>
, it will still fire on each load with this code.
Regarding the load event
Here is a test case demonstrating that you don't need an inline onload
event. This depends on referencing a file (/emptypage.php) on the same server, otherwise you should be able to just paste this into a page and run it.
<!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>
The alert fires every time I click the submit button in Safari, Firefox, IE 6, 7 and 8.
I suggest the module pattern.
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