2013-09-29

Tips for using window in JavaScript

In web browsers, window refers to an object that contains the global variables. This blog post explains how it works and when to use it.

The global object and window

The ECMAScript specification uses the internal data structure environment to store variables. The language has the somewhat unusual feature of making the environment for global variables accessible as an object, the so-called global object. The global object can be used to create, read and change global variables. In global scope, this points to it:
    > var foo = 'hello';
    > this.foo  // read global variable
    'hello'

    > this.bar = 'world';  // create global variable
    > bar
    'world'
In web browsers, the global variable window also points to the global object.

JavaScript creator Brendan Eich considers the global object one of his “biggest regrets”.

Cross-platform considerations

Node.js does not have a global variable window. Therefore, the only safe cross-platform way to refer to the global object is via this and only in global scope. You can, however store a reference to it in a variable, via an IIFE [1].
    (function (glob) {  // open IIFE
        // glob points to global object
    }(this));  // close IIFE
From now on, I use window to refer to the global object, but in cross-platform code, you should use the above pattern and glob, instead.

Use cases for window

Marking global variables

The prefix window is a visual clue that one is referring to a global variable and not to a local one:
    var foo = 123;
    (function () {
        console.log(window.foo);  // 123
    }());
However, this makes your code brittle. It ceases to work as soon as you move foo from global scope to another surrounding scope.
    (function () {
        var foo = 123;
        console.log(window.foo);  // undefined
    }());
Thus, it is better to refer to foo as a variable, not as a property of window. If you want to make it obvious that foo is a global or global-like variable, you can add a name prefix such as g_.
    var g_foo = 123;
    (function () {
        console.log(g_foo);
    }());
On the other hand, you need to use window if you want to protect yourself from a global variable being shadowed by in inner scope.

Built-ins. I prefer not to refer to built-in global variables via window. They are well-known names, so you gain little from an indicator that they are global. And the prefixed window adds clutter:

    window.isNaN(...)  // no
    isNaN(...)  // yes

Style checkers. When working with a style checking tool such as JSLint and JSHint, using window means that you don’t get an error when referring to a global variable that is not declared in the current file. However, both tools provide ways for telling them about such variables and preventing such errors (search for “global variable” in their documentation).

Checking whether a global variable exists

window enables you to check whether a global variable exists:
    if (window.someVariable) { ... }
This is a safe way of performing this check. The following statement throws an exception if someVariable has not been declared:
    // Don’t do this
    if (someVariable) { ... }
There are two additional ways in which you can check via window that are roughly equivalent, but a little more explicit:
    if (window.someVariable !== undefined) { ... }
    if ('someVariable' in window) { ... }
However, an even more explicit way is to use typeof:
    if (typeof someVariable !== 'undefined') { ... }

Creating things in global scope

window lets you add things to the global scope (even if you are in a nested scope) and it lets you do so conditionally:
    if (!window.someApiFunction) {
        window.someApiFunction = ...;
    }
It is normally best to add things to the global scope while you are in the global scope. But adding things conditionally can only be done cleanly via window.

Anti-pattern. Don’t create a global variable by assigning to it without declaring it:

    function sloppyFunction() {
        globalVariable = 123;
    }
This is a deprecated feature and only works in sloppy mode. In strict mode, you get an error:
    function strictFunction() {
        'use strict';
        globalVariable = 123;
    }
    strictFunction();
    // ReferenceError: globalVariable is not defined

The future of global variables

With modules in ECMAScript 6, variables in global scope will become less common: Variables you create in the top scope of a module are only global to that module. Furthermore, new language functionality will mostly be added via modules from now on, not via global variables. Consequently, polyfilling [2] on older engines will probably be done via modules, too.

Conclusion

We have seen several use cases for window. But you can normally achieve the same result by treating a global variable as a variable and not as a property of window. This is the recommended way of doing it, because your code becomes slightly more flexible and because the global scope will become less used in the future.

The idea for this blog post came from the answers of a tweet, where I asked why and how people use window.

References

  1. JavaScript variable scoping and its pitfalls
  2. What is the difference between a shim and a polyfill?
  3. JavaScript’s strict mode: a summary

No comments: