Wszyscy wiemy, że zmienne globalne nie są najlepszą praktyką. Ale jest kilka przypadków, w których trudno jest kodować bez nich. Jakich technik używasz, aby uniknąć używania zmiennych globalnych?
Na przykład, biorąc pod uwagę następujący scenariusz, w jaki sposób nie użyłbyś zmiennej globalnej?
Kod 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();
}
Odpowiednie znaczniki:
<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. -->
Ten kod pochodzi z formularza internetowego z wieloma plikami <input type="file">
. Przesyła pliki pojedynczo, aby zapobiec ogromnym żądaniom. Robi to przez POST w elemencie iframe, czekając na odpowiedź, która uruchamia element iframe podczas ładowania, a następnie wyzwala następne przesłanie.
Nie musisz konkretnie odpowiadać na ten przykład, podam go tylko jako odniesienie do sytuacji, w której nie jestem w stanie wymyślić sposobu na uniknięcie zmiennych globalnych.
źródło
Odpowiedzi:
Najłatwiejszym sposobem jest zawinięcie kodu w zamknięcie i ręczne udostępnienie globalnie tylko tych zmiennych, których potrzebujesz:
(function() { // Your code here // Expose to global window['varName'] = varName; })();
Aby odnieść się do komentarza Crescent Fresh: aby całkowicie usunąć zmienne globalne ze scenariusza, deweloper musiałby zmienić szereg założeń w pytaniu. Wyglądałoby to bardziej tak:
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>
Nie potrzebujesz wbudowanej obsługi zdarzeń w programie
<iframe>
, będzie on nadal uruchamiany przy każdym ładowaniu z tym kodem.Odnośnie zdarzenia obciążenia
Oto przypadek testowy pokazujący, że nie potrzebujesz
onload
zdarzenia inline . Zależy to od odniesienia do pliku (/emptypage.php) na tym samym serwerze, w przeciwnym razie powinieneś być w stanie po prostu wkleić to na stronę i uruchomić.<!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>
Alert jest uruchamiany za każdym razem, gdy klikam przycisk przesyłania w Safari, Firefox, IE 6, 7 i 8.
źródło
var foo = "bar"; (function() { alert(window['foo']) })();
Proponuję wzór modułu .
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
źródło
YAHOO.myProject.myModule
którym jest zmienna globalna. Dobrze?Po pierwsze, nie da się uniknąć globalnego JavaScript, zawsze coś będzie wisiało w zasięgu globalnym. Nawet jeśli utworzysz przestrzeń nazw, co nadal jest dobrym pomysłem, będzie ona globalna.
Istnieje jednak wiele podejść, aby nie nadużywać zasięgu globalnego. Dwa z najprostszych to użycie domknięcia lub, ponieważ masz tylko jedną zmienną, którą musisz śledzić, po prostu ustaw ją jako właściwość samej funkcji (która może być następnie traktowana jako
static
zmienna).Zamknięcie
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(); }; })();
* Zwróć uwagę, że przyrost wartości
uploadCount
odbywa się tutaj wewnętrznieWłaściwość funkcji
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(); };
Nie jestem pewien, dlaczego
uploadCount++; if(uploadCount > 1) ...
jest to konieczne, ponieważ wygląda na to, że warunek zawsze będzie prawdziwy. Ale jeśli potrzebujesz globalnego dostępu do zmiennej, metoda właściwości funkcji , którą opisałem powyżej, pozwoli ci to zrobić bez rzeczywistego globalnego dostępu do zmiennej .<iframe src="test.htm" name="postHere" id="postHere" onload="startUpload.count++; if (startUpload.count > 1) startUpload();"></iframe>
Jeśli jednak tak jest, to prawdopodobnie powinieneś użyć dosłownego obiektu lub obiektu, dla którego utworzono instancję i przejść do tego w normalny sposób OO (gdzie możesz użyć wzorca modułu, jeśli ci się spodoba).
źródło
startUpload
jest to pojedyncza zmienna, do której się odnosisz . Usunięcievar startUpload =
z przykładu zamknięcia oznacza, że funkcja wewnętrzna nigdy nie będzie mogła zostać wykonana, ponieważ nie ma do niej odniesienia. Kwestia unikania zanieczyszczenia o zasięgu globalnym dotyczy zmiennej licznika wewnętrznego, zuploadCount
której korzystastartUpload
. Co więcej, myślę, że PO stara się uniknąć zanieczyszczenia jakiegokolwiek zakresu poza tą metodąuploadCount
zmienną używaną wewnętrznie .Czasami warto mieć zmienne globalne w JavaScript. Ale nie zostawiaj ich tak zwisających bezpośrednio z okna.
Zamiast tego utwórz pojedynczy obiekt „przestrzeni nazw”, który będzie zawierał Twoje elementy globalne. Aby otrzymać punkty bonusowe, umieść tam wszystko, w tym metody.
źródło
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); } }
źródło
onload
obsługi w elemencie iframe? To jest kluczowe.onload
wymagany jest wbudowany moduł obsługi. Programowe przypisywanie programu obsługi nie działa, ponieważ jest uruchamiany tylko za pierwszym razem. Wbudowany program obsługi uruchamia się za każdym razem (z dowolnego powodu).Innym sposobem jest utworzenie obiektu, a następnie dodanie do niego metod.
var object = { a = 21, b = 51 }; object.displayA = function() { console.log(object.a); }; object.displayB = function() { console.log(object.b); };
W ten sposób ujawniany jest tylko obiekt „obj” i metody do niego dołączane. Jest to równoważne dodaniu go w przestrzeni nazw.
źródło
Niektóre rzeczy będą znajdować się w globalnej przestrzeni nazw - mianowicie, niezależnie od funkcji, którą wywołujesz z wbudowanego kodu JavaScript.
Ogólnie rozwiązaniem jest zawinięcie wszystkiego w zamknięcie:
(function() { var uploadCount = 0; function startupload() { ... } document.getElementById('postHere').onload = function() { uploadCount ++; if (uploadCount > 1) startUpload(); }; })();
i unikaj obsługi wbudowanej.
źródło
iframe.onload = ...
nie jest równoważne<iframe onload="..."
?Używanie domknięć może być OK w przypadku małych i średnich projektów. Jednak w przypadku dużych projektów warto podzielić kod na moduły i zapisać je w różnych plikach.
Dlatego napisałem wtyczkę jQuery Secret, aby rozwiązać problem.
W twoim przypadku z tą wtyczką kod wyglądałby mniej więcej tak.
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; } }
Odpowiednie znaczniki:
<iframe src="test.htm" name="postHere" id="postHere" onload="$.secret( 'call', 'iframeOnload' );"></iframe>
Otwórz swojego Firebuga , nie znajdziesz w ogóle żadnych globali, nawet funkcji :)
Pełną dokumentację można znaleźć tutaj .
Aby zobaczyć stronę demonstracyjną, zobacz to .
Kod źródłowy na GitHub .
źródło
Użyj zamknięć. Coś takiego daje ci zakres inny niż globalny.
(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 })();
źródło
W celu „zabezpieczenia” poszczególnych zmiennych globalnych:
function gInitUploadCount() { var uploadCount = 0; gGetUploadCount = function () { return uploadCount; } gAddUploadCount= function () { uploadCount +=1; } } gInitUploadCount(); gAddUploadCount(); console.log("Upload counter = "+gGetUploadCount());
Jestem nowicjuszem w JS, obecnie używam tego w jednym projekcie. (doceniam wszelkie komentarze i krytykę)
źródło
Używam tego w ten sposób:
{ var globalA = 100; var globalB = 200; var globalFunc = function() { ... } let localA = 10; let localB = 20; let localFunc = function() { ... } localFunc(); }
Dla wszystkich zasięgów globalnych użyj „var”, a dla zakresów lokalnych użyj „let”.
źródło