Why Global Scope?

It’s likely no surprise to readers that most applications are composed of multiple (sometimes many!) individual JS files. So how exactly do all those separate files get stitched together in a single runtime context by the JS engine?

With respect to browser-executed applications, there are three main ways.

First, if you’re directly using ES modules (not transpiling them into some other module-bundle format), these files are loaded individually by the JS environment. Each module then imports references to whichever other modules it needs to access. The separate module files cooperate with each other exclusively through these shared imports, without needing any shared outer scope.

Second, if you’re using a bundler in your build process, all the files are typically concatenated together before delivery to the browser and JS engine, which then only processes one big file. Even with all the pieces of the application co-located in a single file, some mechanism is necessary for each piece to register a name to be referred to by other pieces, as well as some facility for that access to occur.

In some build setups, the entire contents of the file are wrapped in a single enclosing scope, such as a wrapper function, universal module (UMD—see Appendix A), etc. Each piece can register itself for access from other pieces by way of local variables in that shared scope. For example:

  1. (function wrappingOuterScope(){
  2. var moduleOne = (function one(){
  3. // ..
  4. })();
  5. var moduleTwo = (function two(){
  6. // ..
  7. function callModuleOne() {
  8. moduleOne.someMethod();
  9. }
  10. // ..
  11. })();
  12. })();

As shown, the moduleOne and moduleTwo local variables inside the wrappingOuterScope() function scope are declared so that these modules can access each other for their cooperation.

While the scope of wrappingOuterScope() is a function and not the full environment global scope, it does act as a sort of “application-wide scope,” a bucket where all the top-level identifiers can be stored, though not in the real global scope. It’s kind of like a stand-in for the global scope in that respect.

And finally, the third way: whether a bundler tool is used for an application, or whether the (non-ES module) files are simply loaded in the browser individually (via <script> tags or other dynamic JS resource loading), if there is no single surrounding scope encompassing all these pieces, the global scope is the only way for them to cooperate with each other:

A bundled file of this sort often looks something like this:

  1. var moduleOne = (function one(){
  2. // ..
  3. })();
  4. var moduleTwo = (function two(){
  5. // ..
  6. function callModuleOne() {
  7. moduleOne.someMethod();
  8. }
  9. // ..
  10. })();

Here, since there is no surrounding function scope, these moduleOne and moduleTwo declarations are simply dropped into the global scope. This is effectively the same as if the files hadn’t been concatenated, but loaded separately:

module1.js:

  1. var moduleOne = (function one(){
  2. // ..
  3. })();

module2.js:

  1. var moduleTwo = (function two(){
  2. // ..
  3. function callModuleOne() {
  4. moduleOne.someMethod();
  5. }
  6. // ..
  7. })();

If these files are loaded separately as normal standalone .js files in a browser environment, each top-level variable declaration will end up as a global variable, since the global scope is the only shared resource between these two separate files—they’re independent programs, from the perspective of the JS engine.

In addition to (potentially) accounting for where an application’s code resides during runtime, and how each piece is able to access the other pieces to cooperate, the global scope is also where:

  • JS exposes its built-ins:

    • primitives: undefined, null, Infinity, NaN
    • natives: Date(), Object(), String(), etc.
    • global functions: eval(), parseInt(), etc.
    • namespaces: Math, Atomics, JSON
    • friends of JS: Intl, WebAssembly
  • The environment hosting the JS engine exposes its own built-ins:

    • console (and its methods)
    • the DOM (window, document, etc)
    • timers (setTimeout(..), etc)
    • web platform APIs: navigator, history, geolocation, WebRTC, etc.

These are just some of the many globals your programs will interact with.

NOTE:
Node also exposes several elements “globally,” but they’re technically not in the global scope: require(), __dirname, module, URL, and so on.

Most developers agree that the global scope shouldn’t just be a dumping ground for every variable in your application. That’s a mess of bugs just waiting to happen. But it’s also undeniable that the global scope is an important glue for practically every JS application.