如何避免JavaScript中的全局variables?

我们都知道全局variables不过是最佳实践。 但有几个例子,如果没有它们就很难编码。 你用什么技术来避免使用全局variables?

例如,给定以下情况,你将如何不使用全局variables?

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

相关标记:

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

此代码来自具有多个<input type="file">的Web表单。 它一次上传一个文件,以防止巨大的请求。 它通过发送到iframe来执行此操作,等待启动iframe的响应,然后触发下一个提交。

你不必特别回答这个例子,我只是提供它来参考一个我无法想到避免全局variables的方法的情况。

最简单的方法是将代码封装在一个闭包中,并手动将全局所需的variables全局暴露给全局范围:

 (function() { // Your code here // Expose to global window['varName'] = varName; })(); 

为了解决Crescent Fresh的评论:为了完全消除场景中的全局variables,开发人员需要改变问题中假定的一些事情。 它看起来更像这样:

使用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> 

您不需要<iframe>上的内联事件处理程序,它仍会使用此代码触发每个负载。

关于负载事件

以下是一个testing用例,演示您不需要内联onload事件。 这取决于在同一台服务器上引用一个文件(/emptypage.php),否则你应该能够把它粘贴到一个页面中并运行它。

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

每次点击Safari,Firefox,IE 6,7和8中的提交button时,都会触发警报。

我build议模块模式 。

 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 

首先,要避免全局JavaScript是不可能的,总是会在全球范围内摇摆不定。 即使你创build一个命名空间,这仍然是一个好主意,这个命名空间将是全局的。

然而,有许多方法不滥用全球范围。 其中两个最简单的方法是使用闭包,或者因为只有一个需要跟踪的variables,只需将其设置为函数本身的属性(然后可以将其视为staticvariables)。

closures

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

*请注意, uploadCount递增是在这里发生的

function属性

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

我不知道为什么uploadCount++; if(uploadCount > 1) ... uploadCount++; if(uploadCount > 1) ...是必要的,因为它看起来情况将永远是真实的。 但是,如果你确实需要全局访问variables,那么上面描述的函数属性方法将允许你在variables实际上不是全局的情况下这样做。

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

但是,如果是这样的话,那么你应该使用一个对象字面值或实例化的对象,并以正常的OO方式来处理这个问题(如果你觉得这样的话,你可以使用模块模式)。

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

有时在JavaScript中使用全局variables是有意义的。 但是不要让它们像这样直接挂在窗外。

相反,创build一个“名称空间”对象来包含您的全局variables。 对于奖励积分,把所有的东西放在那里,包括你的方法。

有些东西将会在全局的命名空间里 – 也就是你从内联JavaScript代码中调用的任何函数。

一般来说,解决scheme是将所有内容都封装在一个闭包中:

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

并避免内联处理程序。

对于中小型项目,使用闭包可能没问题。 但是,对于大型项目,您可能希望将代码拆分为模块并将其保存在不同的文件中。

所以我写了jQuery Secret插件来解决这个问题。

在你的这个插件的情况下,代码将如下所示。

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

相关标记:

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

打开你的Firebug ,你根本找不到全局的,甚至没有function:)

有关完整的文档,请参阅此处 。

对于演示页面,请看这个 。

GitHub上的源代码 。

其他的方法是创build一个对象,然后添加方法。

 var object = { a = 21, b = 51 }; object.displayA = function() { console.log(object.a); }; object.displayB = function() { console.log(object.b); }; 

通过这种方式,只有对象'obj'被暴露,方法附加到它。 这相当于将其添加到名称空间中。

使用闭包。 像这样的东西给你一个范围以外的全球。

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

为了“确保”个体的全局variables:

 function gInitUploadCount() { var uploadCount = 0; gGetUploadCount = function () { return uploadCount; } gAddUploadCount= function () { uploadCount +=1; } } gInitUploadCount(); gAddUploadCount(); console.log("Upload counter = "+gGetUploadCount());