Wir alle wissen, dass globale Variablen alles andere als Best Practice sind. Es gibt jedoch mehrere Fälle, in denen es schwierig ist, ohne sie zu programmieren. Welche Techniken verwenden Sie, um die Verwendung globaler Variablen zu vermeiden?
Wie würden Sie in dem folgenden Szenario beispielsweise keine globale Variable verwenden?
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();
}
Relevantes 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. -->
Dieser Code stammt aus einem Webformular mit mehreren <input type="file">
. Es lädt die Dateien einzeln hoch, um große Anforderungen zu vermeiden. Dies geschieht, indem POST an den Iframe gesendet wird, auf die Antwort gewartet wird, die den Iframe-Onload auslöst, und dann die nächste Übermittlung ausgelöst wird.
Sie müssen dieses Beispiel nicht spezifisch beantworten, sondern geben es nur als Referenz für eine Situation an, in der ich mir keine Möglichkeit vorstellen kann, globale Variablen zu vermeiden.
Am einfachsten ist es, den Code in einen Closure zu packen und nur die Variablen, die Sie global benötigen, manuell für den globalen Bereich verfügbar zu machen:
(function() {
// Your code here
// Expose to global
window['varName'] = varName;
})();
Um auf den Kommentar von Crescent Fresh einzugehen: Um globale Variablen vollständig aus dem Szenario zu entfernen, müsste der Entwickler eine Reihe von Dingen ändern, die in der Frage angenommen wurden. Es würde viel mehr so aussehen:
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>
Sie benötigen keinen Inline-Event-Handler auf dem <iframe>
, es wird weiterhin bei jedem Laden mit diesem Code ausgelöst.
Bezüglich des Ladeereignisses
Hier ist ein Testfall, der zeigt, dass Sie kein Inline-Ereignis onload
benötigen. Dies hängt davon ab, ob Sie auf eine Datei (/emptypage.php) auf demselben Server verweisen. Andernfalls sollten Sie diese einfach in eine Seite einfügen und ausführen können.
<!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>
Die Warnung wird jedes Mal ausgelöst, wenn ich in Safari, Firefox, IE 6, 7 und 8) auf die Schaltfläche zum Senden klicke.
Ich schlage das Modulmuster vor.
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
Zuallererst ist es unmöglich, globales JavaScript zu vermeiden, etwas wird immer den globalen Rahmen sprengen. Auch wenn Sie einen Namespace erstellen, was immer noch eine gute Idee ist, ist dieser Namespace global.
Es gibt jedoch viele Ansätze, um den globalen Geltungsbereich nicht zu missbrauchen. Zwei der einfachsten sind entweder Closure oder, da Sie nur eine Variable haben, die Sie im Auge behalten müssen, setzen Sie sie einfach als eine Eigenschaft der Funktion selbst (die dann als static
-Variable behandelt werden kann). .
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();
};
})();
* Beachten Sie, dass das Inkrementieren von uploadCount
hier intern erfolgt
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();
};
Ich bin mir nicht sicher, warum uploadCount++; if(uploadCount > 1) ...
notwendig ist, da es so aussieht, als ob die Bedingung immer wahr sein wird. Wenn Sie jedoch globalen Zugriff auf die Variable benötigen, können Sie dies mit der oben beschriebenen Funktionseigenschaft -Methode tun, ohne dass die Variable tatsächlich global ist.
<iframe src="test.htm" name="postHere" id="postHere"
onload="startUpload.count++; if (startUpload.count > 1) startUpload();"></iframe>
Wenn dies jedoch der Fall ist, sollten Sie wahrscheinlich ein Objektliteral oder ein instanziiertes Objekt verwenden und wie gewohnt vorgehen OO (wobei Sie das Modulmuster verwenden können, wenn es Ihnen gefällt) .
Manchmal ist es sinnvoll, globale Variablen in JavaScript zu haben. Aber lass sie nicht so direkt am Fenster hängen.
Erstellen Sie stattdessen ein einzelnes "Namespace" -Objekt, das Ihre globalen Elemente enthält. Setzen Sie für Bonuspunkte alles ein, einschließlich Ihrer Methoden.
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);
}
}
Eine andere Möglichkeit besteht darin, ein Objekt zu erstellen und ihm dann Methoden hinzuzufügen.
var object = {
a = 21,
b = 51
};
object.displayA = function() {
console.log(object.a);
};
object.displayB = function() {
console.log(object.b);
};
Auf diese Weise wird nur das Objekt 'obj' verfügbar gemacht und Methoden daran angehängt. Dies entspricht dem Hinzufügen im Namespace.
Einige Dinge werden sich im globalen Namespace befinden - nämlich welche Funktion Sie auch immer aus Ihrem Inline-JavaScript-Code aufrufen.
Im Allgemeinen besteht die Lösung darin, alles in einem Verschluss zu verpacken:
(function() {
var uploadCount = 0;
function startupload() { ... }
document.getElementById('postHere').onload = function() {
uploadCount ++;
if (uploadCount > 1) startUpload();
};
})();
und vermeiden Sie den Inline-Handler.
Die Verwendung von Verschlüssen ist möglicherweise für kleine bis mittlere Projekte in Ordnung. Bei großen Projekten empfiehlt es sich jedoch, den Code in Module aufzuteilen und in verschiedenen Dateien zu speichern.
Deshalb habe ich jQuery Secret plugin geschrieben, um das Problem zu lösen.
In Ihrem Fall würde der Code mit diesem Plugin wie folgt aussehen.
// 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>
Öffne dein Firebug , du wirst überhaupt keine Globals finden, nicht einmal die Funktion :)
Zum "Sichern" einzelner globaler Variablen:
function gInitUploadCount() {
var uploadCount = 0;
gGetUploadCount = function () {
return uploadCount;
}
gAddUploadCount= function () {
uploadCount +=1;
}
}
gInitUploadCount();
gAddUploadCount();
console.log("Upload counter = "+gGetUploadCount());
Ich bin ein Anfänger von JS und benutze dies derzeit in einem Projekt. (Ich schätze jeden Kommentar und jede Kritik)
Verschlüsse verwenden. So etwas gibt Ihnen einen anderen Geltungsbereich als 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
})();