Jak uniknąć zmiennych globalnych w JavaScript?

Wszyscy wiemy, że zmienne globalne nie są najlepszą praktyką. Ale jest kilka przypadków, gdy trudno jest kodować bez nich. Jakich technik używasz, aby uniknąć użycia zmiennych globalnych?

Na przykład, biorąc pod uwagę następujący scenariusz, jak nie można użyć 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 <input type="file">. Wgrywa pliki jeden po drugim, aby zapobiec wielkim prośbom. Robi to przez POST ing do iframe, czekając na odpowiedź, która uruchamia iFrame onload, a następnie uruchamia następne przesyłanie.

Nie musisz odpowiadać konkretnie na ten przykład, po prostu podaję go w odniesieniu do sytuacji, w której nie jestem w stanie wymyślić sposobu na uniknięcie zmiennych globalnych.

Author: Peter Mortensen, 2009-12-03

10 answers

Najprostszym sposobem jest zawinięcie kodu w zamknięcie i ręczne ujawnienie tylko tych zmiennych, których potrzebujesz globalnie, do globalnego zakresu:

(function() {
    // Your code here

    // Expose to global
    window['varName'] = varName;
})();

W odpowiedzi na komentarz Crescent Fresh: aby całkowicie usunąć zmienne globalne ze scenariusza, programista musiałby zmienić kilka rzeczy zakładanych w pytaniu. Wyglądałoby to o wiele 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 wbudowanego obsługi zdarzeń na <iframe>, nadal będzie strzelać przy każdym ładowaniu z tym kodem.

Regarding the load event

Oto przypadek testowy pokazujący, że nie potrzebujesz zdarzenia inline onload. Zależy to od odwołania się 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 submit w Safari, Firefox, IE 6, 7 i 8.

 63
Author: eyelidlessness,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-04 18:22:11

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
 54
Author: erenon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-03 18:38:10

Po pierwsze, nie da się uniknąć globalnego JavaScript, coś zawsze będzie wisiało globalny zasięg. Nawet jeśli utworzysz przestrzeń nazw, co nadal jest dobrym pomysłem, ta przestrzeń nazw będzie globalna.

Istnieje jednak wiele sposobów, aby nie nadużywać globalnego zasięgu. Dwa z najprostszych to albo użycie closure, albo 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();
  };
})();

* zauważ, że inkrementacja uploadCount dzieje się wewnętrznie tutaj

Wł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 jest to konieczne, ponieważ wygląda na to, że warunek zawsze będzie prawdziwy. Ale jeśli potrzebujesz globalnego dostępu do zmiennej, to metoda function property, którą opisałem powyżej, pozwoli Ci to zrobić bez tego, że zmienna jest rzeczywiście globalna.
<iframe src="test.htm" name="postHere" id="postHere"
  onload="startUpload.count++; if (startUpload.count > 1) startUpload();"></iframe>

Jednakże, jeśli tak jest, to należy prawdopodobnie używaj obiektu dosłownego lub instancyjnego obiektu i rób to w normalny sposób OO (gdzie możesz użyć wzorca modułu, jeśli ci się spodoba).

 7
Author: Justin Johnson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-15 16:37:05
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);  
    }       
}
 4
Author: ChaosPandion,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-03 18:46:05

Czasami sensowne jest posiadanie zmiennych globalnych w JavaScript. Ale nie zostawiaj ich zwisających z okna w ten sposób.

Zamiast tego utwórz pojedynczy obiekt "przestrzeni nazw", który będzie zawierał Twoje globale. Aby uzyskać punkty bonusowe, umieść tam wszystko, w tym swoje metody.

 3
Author: Nosredna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-03 18:36:28

Niektóre rzeczy będą w globalnej przestrzeni nazw-mianowicie, niezależnie od funkcji, którą wywołujesz z wbudowanego kodu JavaScript.

Ogólnie rzecz biorąc, rozwiązaniem jest owinię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 inline.

 1
Author: Jimmy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-19 17:29:29

Używanie zamknięć może być OK dla małych i średnich projektów. Jednak w przypadku dużych projektów możesz podzielić kod na moduły i zapisać je w różnych plikach.

Dlatego napisałem jQuery Secret plugin aby rozwiązać problem.

W Twoim przypadku z tą wtyczką kod będzie wyglądał 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;
    }
}

Odpowiedni znacznik:

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

Otwórz swój Firebug , nie znajdziesz żadnego globala, nawet funciton:)

Pełna dokumentacja znajduje się w tutaj .

Aby zobaczyć stronę demo, zobacz to .

Kod źródłowy na GitHub .

 1
Author: ben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-19 18:17:37

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 wyeksponowany jest tylko obiekt " obj " i dołączone do niego metody. Jest to równoznaczne z dodaniem go w przestrzeni nazw.

 1
Author: Ajay Poshak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-06 09:58:21

Użyj zamknięć. Coś takiego daje zasięg 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
})();
 0
Author: NilColor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-03 22:37:22

Dla "zabezpieczenia" 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. (apreciate any comment and criticism)

 0
Author: Koenees,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-14 06:37:35