Todos sabemos que las variables globales no son las mejores prácticas. Pero hay varios casos en los que es difícil codificar sin ellos. ¿Qué técnicas utiliza para evitar el uso de variables globales?
Por ejemplo, dado el siguiente escenario, ¿cómo no usaría una variable global?
Código 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();
}
Marcado relevante:
<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. -->
Este código proviene de un formulario web con varios <input type="file">
. Carga los archivos de uno en uno para evitar grandes solicitudes. Lo hace PUBLICANDO en el iframe, esperando la respuesta que activa la carga del iframe y luego activa el siguiente envío.
No tiene que responder a este ejemplo específicamente, solo lo proporciono como referencia a una situación en la que no puedo pensar en una forma de evitar las variables globales.
fuente
Respuestas:
La forma más fácil es envolver su código en un cierre y exponer manualmente solo aquellas variables que necesita globalmente al alcance global:
(function() { // Your code here // Expose to global window['varName'] = varName; })();
Para abordar el comentario de Crescent Fresh: para eliminar las variables globales del escenario por completo, el desarrollador necesitaría cambiar una serie de cosas asumidas en la pregunta. Se parecería mucho más a esto:
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>
No necesita un controlador de eventos en línea en el
<iframe>
, aún se activará en cada carga con este código.Respecto al evento de carga
Aquí hay un caso de prueba que demuestra que no necesita un
onload
evento en línea . Esto depende de hacer referencia a un archivo (/emptypage.php) en el mismo servidor; de lo contrario, debería poder pegarlo en una página y ejecutarlo.<!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>
La alerta se activa cada vez que hago clic en el botón Enviar en Safari, Firefox, IE 6, 7 y 8.
fuente
var foo = "bar"; (function() { alert(window['foo']) })();
Sugiero el patrón del módulo .
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
fuente
YAHOO.myProject.myModule
, que es una variable global. ¿Derecho?En primer lugar, es imposible evitar JavaScript global, siempre habrá algo pendiente del alcance global. Incluso si crea un espacio de nombres, lo cual sigue siendo una buena idea, ese espacio de nombres será global.
Sin embargo, existen muchos enfoques para no abusar del alcance global. Dos de los más simples son usar el cierre o, dado que solo tiene una variable de la que necesita realizar un seguimiento, simplemente configúrela como una propiedad de la función en sí (que luego puede tratarse como una
static
variable).Cierre
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(); }; })();
* Tenga en cuenta que el incremento de
uploadCount
ocurre internamente aquíPropiedad de la función
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(); };
No estoy seguro de por qué
uploadCount++; if(uploadCount > 1) ...
es necesario, ya que parece que la condición siempre será cierta. Pero si necesita acceso global a la variable, entonces el método de propiedad de la función que describí anteriormente le permitirá hacerlo sin que la variable sea realmente global.<iframe src="test.htm" name="postHere" id="postHere" onload="startUpload.count++; if (startUpload.count > 1) startUpload();"></iframe>
Sin embargo, si ese es el caso, entonces probablemente debería usar un objeto literal o instanciado y hacer esto de la manera normal de OO (donde puede usar el patrón del módulo si le apetece).
fuente
startUpload
es esa única variable a la que te refieres. Eliminarvar startUpload =
del ejemplo de cierre significa que la función interna nunca podrá ejecutarse porque no hay ninguna referencia a ella. La cuestión de evitar la contaminación de alcance global se refiere a la contra variable interna,uploadCount
que es utilizada porstartUpload
. Además, creo que el OP está tratando de evitar contaminar cualquier alcance fuera de este método con lauploadCount
variable utilizada internamente .A veces tiene sentido tener variables globales en JavaScript. Pero no los deje colgando directamente de la ventana de esa manera.
En su lugar, cree un único objeto de "espacio de nombres" para contener sus globales. Para obtener puntos de bonificación, ponga todo allí, incluidos sus métodos.
fuente
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); } }
fuente
onload
controlador en el iframe? Esto es crucial.onload
se requiere el controlador en línea . La asignación programática del controlador no funciona porque solo se activa la primera vez. El controlador en línea se activa cada vez (por el motivo que sea).Otra forma de hacer esto es crear un objeto y luego agregarle métodos.
var object = { a = 21, b = 51 }; object.displayA = function() { console.log(object.a); }; object.displayB = function() { console.log(object.b); };
De esta manera, solo se expone el objeto 'obj' y los métodos que se le atribuyen. Es equivalente a agregarlo en el espacio de nombres.
fuente
Algunas cosas estarán en el espacio de nombres global, es decir, cualquier función que esté llamando desde su código JavaScript en línea.
En general, la solución es envolver todo en un cierre:
(function() { var uploadCount = 0; function startupload() { ... } document.getElementById('postHere').onload = function() { uploadCount ++; if (uploadCount > 1) startUpload(); }; })();
y evitar el controlador en línea.
fuente
iframe.onload = ...
no es equivalente a<iframe onload="..."
?El uso de cierres puede estar bien para proyectos pequeños o medianos. Sin embargo, para proyectos grandes, es posible que desee dividir su código en módulos y guardarlos en diferentes archivos.
Por lo tanto, escribí el complemento jQuery Secret para resolver el problema.
En su caso, con este complemento, el código se parecería al siguiente.
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; } }
Marcado relevante:
<iframe src="test.htm" name="postHere" id="postHere" onload="$.secret( 'call', 'iframeOnload' );"></iframe>
Abra su Firebug , no encontrará globales en absoluto, ni siquiera la función :)
Para obtener la documentación completa, consulte aquí .
Para ver una página de demostración, consulte esto .
Código fuente en GitHub .
fuente
Utilice cierres. Algo como esto le da un alcance diferente al 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 })();
fuente
Para "asegurar" variables globales induviduales:
function gInitUploadCount() { var uploadCount = 0; gGetUploadCount = function () { return uploadCount; } gAddUploadCount= function () { uploadCount +=1; } } gInitUploadCount(); gAddUploadCount(); console.log("Upload counter = "+gGetUploadCount());
Soy un novato en JS, actualmente estoy usando esto en un proyecto. (agradezco cualquier comentario y crítica)
fuente
Lo uso de esta manera:
{ var globalA = 100; var globalB = 200; var globalFunc = function() { ... } let localA = 10; let localB = 20; let localFunc = function() { ... } localFunc(); }
Para todos los ámbitos globales use 'var', y para los ámbitos locales use 'let'.
fuente