How to avoid global variables in JavaScript?

JavascriptGlobal Variables

Javascript Problem Overview


We all know that global variables are anything but best practice. But there are several instances when it is difficult to code without them. What techniques do you use to avoid the use of global variables?

For example, given the following scenario, how would you not use a global variable?

JavaScript code:

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();
}

Relevant markup:

<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. -->

This code comes from a web form with multiple <input type="file">. It uploads the files one at a time to prevent huge requests. It does this by POSTing to the iframe, waiting for the response which fires the iframe onload, and then triggers the next submission.

You don't have to answer this example specifically, I am just providing it for reference to a situation in which I am unable to think of a way to avoid global variables.

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

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

Solution 3 - Javascript

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).

Solution 4 - 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.

Solution 5 - Javascript

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);  
	} 		
}

Solution 6 - Javascript

Some things are going to be in the global namespace -- namely, whatever function you're calling from your inline JavaScript code.

In general, the solution is to wrap everything in a closure:

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

and avoid the inline handler.

Solution 7 - Javascript

Other way to do this is to create an object and then add methods to it.

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

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

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

In this way, only object 'obj' is exposed and methods attached to it. It is equivalent to adding it in namespace.

Solution 8 - Javascript

Using closures might be OK for small to medium projects. However, for big projects, you might want to split your code into modules and save them in different files.

Therefore I wrote jQuery Secret plugin to solve the problem.

In your case with this plugin the code would look something like the following.

###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;
    }
}

###Relevant markup:

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

Open your Firebug, you will find no globals at all, not even the funciton :)

For full documentation, please see here.
For a demo page, please see this.
Source code on GitHub.

Solution 9 - Javascript

I use it this way:

{
    var globalA = 100;
    var globalB = 200;
    var globalFunc = function() { ... }

    let localA = 10;
    let localB = 20;
    let localFunc = function() { ... }

    localFunc();
}

For all global scopes use 'var', and for local scopes use 'let'.

Solution 10 - Javascript

Use closures. Something like this gives you a scope other than global.

(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
})();

Solution 11 - Javascript

For "securing" induvidual global variables:

function gInitUploadCount() {
    var uploadCount = 0;

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

gInitUploadCount();
gAddUploadCount();

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

I'm a novice to JS, currently using this in one project. (i apreciate any comment and criticism)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJosh StodolaView Question on Stackoverflow
Solution 1 - JavascripteyelidlessnessView Answer on Stackoverflow
Solution 2 - JavascripterenonView Answer on Stackoverflow
Solution 3 - JavascriptJustin JohnsonView Answer on Stackoverflow
Solution 4 - JavascriptNosrednaView Answer on Stackoverflow
Solution 5 - JavascriptChaosPandionView Answer on Stackoverflow
Solution 6 - JavascriptJimmyView Answer on Stackoverflow
Solution 7 - JavascriptAjay PoshakView Answer on Stackoverflow
Solution 8 - JavascriptbenView Answer on Stackoverflow
Solution 9 - JavascriptAshwinView Answer on Stackoverflow
Solution 10 - JavascriptNilColorView Answer on Stackoverflow
Solution 11 - JavascriptKoeneesView Answer on Stackoverflow