Hoe globale variabelen in JavaScript te vermijden?

We weten allemaal dat algemene variabelenallesbehalve de beste methode zijn. Maar er zijn verschillende gevallen waarin het moeilijk is om zonder te coderen. Welke technieken gebruik je om het gebruik van globale variabelen te vermijden?

Hoe zou u bijvoorbeeld, gegeven het volgende scenario, geen globale variabele gebruiken?

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

Relevante opmaak:

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

Deze code komt van een webformulier met meerdere <input type="file">. Het uploadt de bestanden één voor één om enorme verzoeken te voorkomen. Het doet dit door POSTing naar het iframe, wachtend op de reactie die de iframe wordt geladen en activeert vervolgens de volgende inzending.

U hoeft dit voorbeeld niet specifiek te beantwoorden, ik geef het alleen ter referentie aan een situatie waarin ik geen manier kan bedenken om globale variabelen te vermijden.


Antwoord 1, autoriteit 100%

De eenvoudigste manier is om uw code in een afsluiting te wikkelen en handmatig alleen die variabelen die u globaal nodig heeft, bloot te stellen aan het globale bereik:

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

Om in te gaan op de opmerking van Crescent Fresh: om globale variabelen volledig uit het scenario te verwijderen, zou de ontwikkelaar een aantal dingen moeten veranderen die in de vraag worden verondersteld. Het zou er veel meer zo uitzien:

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>

Je hebt geen nodigeen inline event handler op de <iframe>, het wordt nog steeds geactiveerd bij elke lading met deze code.

Met betrekking tot de laadgebeurtenis

Hier is een testcase die aantoont dat je geen inline onload-gebeurtenis nodig hebt. Dit hangt af van het verwijzen naar een bestand (/emptypage.php) op dezelfde server, anders zou je dit gewoon in een pagina moeten kunnen plakken en uitvoeren.

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

De waarschuwing wordt geactiveerd telkens wanneer ik op de verzendknop klik in Safari, Firefox, IE 6, 7 en 8.


Antwoord 2, autoriteit 84%

Ik raad het modulepatroonaan.

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

Antwoord 3, autoriteit 11%

Ten eerste: het is onmogelijk om wereldwijd JavaScript te vermijden, er zal altijd iets aan het wereldwijde bereik bungelen. Zelfs als je een naamruimte maakt, wat nog steeds een goed idee is, zal die naamruimte globaal zijn.

Er zijn echter veel manieren om de wereldwijde reikwijdte niet te misbruiken. Twee van de eenvoudigste zijn om ofwel sluiting te gebruiken, of aangezien je maar één variabele hebt die je moet bijhouden, stel deze gewoon in als een eigenschap van de functie zelf (die dan kan worden behandeld als een staticvariabele).

Sluiting

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

* Houd er rekening mee dat het verhogen van uploadCounthier intern plaatsvindt

Functie-eigenschap

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

Ik weet niet zeker waarom uploadCount++; if(uploadCount > 1) ...is noodzakelijk, omdat het lijkt alsof de voorwaarde altijd waar zal zijn. Maar als je wel globale toegang tot de variabele nodig hebt, dan kun je met de methode function propertydie ik hierboven heb beschreven dit doen zonder dat de variabele daadwerkelijk globaal is.

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

Als dat echter het geval is, moet u waarschijnlijk een letterlijk object of een geïnstantieerd object gebruiken en dit op de normale OO-manier doen (waar u het modulepatroon kunt gebruiken als u dat wilt).


Antwoord 4, autoriteit 8%

Soms is het logisch om globale variabelen in JavaScript te hebben. Maar laat ze niet zo aan het raam hangen.

Maak in plaats daarvan een enkel “naamruimte”-object om uw globals te bevatten. Vul voor bonuspunten alles in, inclusief je methoden.


Antwoord 5, autoriteit 7%

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

Antwoord 6, autoriteit 4%

Sommige dingen zullen in de globale naamruimte staan ​​– namelijk, welke functie je ook aanroept vanuit je inline JavaScript-code.

Over het algemeen is de oplossing om alles in een sluiting te wikkelen:

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

en vermijd de inline handler.


Antwoord 7, autoriteit 4%

Een andere manier om dit te doen is door een object te maken en er vervolgens methoden aan toe te voegen.

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

Op deze manier wordt alleen object ‘obj’ zichtbaar en worden er methoden aan gekoppeld. Het is gelijk aan het toevoegen aan de naamruimte.


Antwoord 8, autoriteit 3%

Het gebruik van sluitingen is mogelijk geschikt voor kleine tot middelgrote projecten. Voor grote projecten wil je misschien je code opsplitsen in modules en deze in verschillende bestanden opslaan.

Daarom schreef ik jQuery Secret-plug-inom het probleem op te lossen.

In jouw geval met deze plug-in zou de code er ongeveer als volgt uit zien.

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

Relevante opmaak:

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

Open je Firebug, je zult helemaal geen globals vinden, zelfs niet de funciton 🙂

Voor volledige documentatie, zie hier .

Zie ditvoor een demopagina.

Broncode op GitHub.

Other episodes