The global object in JavaScript: a matter of platforms, unreadable code and not breaking the internet

7 min read

This article was initially posted on www.contentful.com

Personally, I think there’s a finite number of situations where I’d consider placing something in the global object. So when I discovered a new TC39 proposal whose aim is to add a new global property to access the global object in Javascript, I was puzzled yet intrigued, and I had to look into it.

# We’re not adding many variables to the global object anymore, are we?

Thinking of front-end code, it’s clear that additional global variables do have a strong use case. Libraries like jQuery place themselves in the global namespace to make their use as easy as possible by just adding a script element to an HTML page.

(function(window) {
  // set something to the global object
  window.$ = {};
})(window);

It’s common practice to use an IIFE (immediately invoked function expression) to prevent variables leaking into the global scope. This IIFE is then executed with the window object to set new properties on it.

For JavaScript code that is supposed to run only in one environment, there is nothing wrong with this approach. For the browser context we can simply pass window (or self or frames), and for the context of Node.js we can use global, but what about JavaScript that should work independently in any environment?

# Universal JavaScript with Browserify

jQuery is clearly not a good example for JavaScript that runs everywhere, so let’s look at a different example. The testing framework Mocha runs in Node.js and the browser. A typical Mocha test file looks as follows:

var assert = require('assert');
describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      assert.equal(-1, [1,2,3].indexOf(4));
    });
  });
});

To write a test in Mocha you have to use the describe and it function. The testing framework places these functions in the global object for you ready to use. The Mocha source code is initially written for the Node.js context which means that the accessible global object is global.

// mocha.js
// setup of mocha emitting the global object
suite.emit('pre-require', global, file, self);

// bdd.js
// actual setting of new global properties
suite.on('pre-require', function (context, file, mocha) {
  var common = require('./common')(suites, context, mocha);

  context.describe = context.context = function (title, fn) {};
  context.it = context.specify = function (title, fn) {};
});

So what does it take to make this code runnable in the browser context, too?

Mocha uses Browserify to build an additional file that can run in the browser context. The build process wraps the code in an IIFE and provides an object named global.

For the sake of simplicity let’s look a simpler example that does nothing else than setting a foo variable to the global scope running in Node.js context.

// test.js
global.foo = 'bar';

After transforming this one line of “Node.js JavaScript” into “browser JavaScript” using browserify we get a rather cryptic result. When we look closer at it we’ll see that the code using the global object is now wrapped in IIFE which provides a global object as a function parameter. The function argument for this parameter is a heavily nested ternary operator checking for the presence of global properties.

(function (global) {
  global.foo = 'bar';
}).call(this, typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {})

I don’t know about you, but this is nothing I’d call beginner friendly or easy to read. Do we really need that many checks to figure out what the global object in an environment is?

# Evaluating the global object is harder than expected

It turns out that there are even more angles to this problem. If we want to write JavaScript that uses the correct global object and can run in any environment, it becomes tricky, and many projects use different approaches to this problem.

So let’s look at the generated browserify output again.

var global = typeof global !== 'undefined' ? 
             global : 
             typeof self !== 'undefined' ? 
               self : 
               typeof window !== 'undefined' ?
               window :
               {};

This code looks one after another for the properties global, self and window to be present. If none of them is defined, it gives up and just assigns a new plain object. This evaluation covers the traditional browser environment, service and web workers and the Node.js context.

# Pretty good — but trial and error doesn't feel right

This approach is neither maintainable nor very future proof and does not cover all the possibilities (yesterday I learned about d8 which is a JavaScript shell coming with V8 that does not include any of these global properties). We don’t know what the future brings and maybe there will be even more properties representing the global object. That means that our evaluation only becomes longer and longer, and uglier and uglier.

# Isn’t this global?

I hear you say that this also refers to the global object (at least sometimes). So why can’t we go with an IIFE and passing this to it?

(function(global) {
  global.foo = 'bar';
})(this);

That’s right! This snippet works, but only if this snippet is not nested inside of another function. Because then this might refer to a changed context or even be undefined (code running in strict mode).

// sloppy.js | works
function getGlobal() {
  return (function(global) {
    return global;
  })(this);
}

console.log(getGlobal()); // window
// strict.js | doesn’t work
'use strict';

function getGlobal() {
  return (function(global) {
    return global;
  })(this);
}

console.log(getGlobal()); // undefined

Relying on this is not a safe option to get the global object in JavaScript. And there is also to say, that ES6 modules will be available at some point and this at top level inside of a module won't reference the global object but rather be undefined (thanks to Axel Rauschmayer for pointing that out).

So what other options do we have?

# The function constructor can help!

Functions are an essential part of any programming language. In JavaScript, there are several ways to create them. The two common ones are function expressions and function declarations, but there is also the not so well known way of using a function constructor.

var fn = new Function('a', 'b', 'return a + b;');
fn(1, 2) // 3

Functions that have been created using the function constructor always run in the global scope. This fact ensures that we’re dealing with the global scope and then using this becomes a safe way to retrieve the current global object.

'use strict';

function getGlobal() {
  return (function(global) {
    return global;
  })(new Function('return this;')());
}

console.log(getGlobal());

This snippet works in strict mode, inside or outside of functions and is probably the best bet we have.

The big downside with the function constructor is that Content Security Policy directives will prevent its execution. CSP helps to reduce the risk of XSS attacks and is a useful technology, but unfortunately using function constructors falls into the category of “unsafe dynamic code evaluation”. So when we want to use function constructors we have to allow dynamic code evaluation and this is most likely something we don't want to do.

# This chaos might be fixed soon

So in the end, it turns out that there is currently no silver bullet to retrieve the real global object in all possible environments. The function constructor is the most accurate one, but it’s not guaranteed that code using it won’t be blocked by CSP directives.

Daniel Ehrenberg had the same feeling and came up with a proposal to make the global object easily accessible to get rid of all these evaluations.

Everybody seemed to like this idea, and the proposal is currently on stage 3 of the TC39 process. One thing that may need further discussions is the actual name of the property that should hold the reference. Most people agreed with global similar to the Node.js environment.

// crappy way | today
(function (global) {
  global.foo = 'bar';
}).call(this, typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {})

// easy way | hopefully future
(function (global) {
  global.foo = 'bar';
}).call(this, global)

At the time of writing, people are validating if this addition may have any negative impact on the web platform itself. Do you remember the drama about Array.prototype.contains? The web doesn’t forget code that was pushed out there. New language features have to be evaluated carefully to be sure that additions and changes are not breaking existing websites.

In fact, it turns out that adding the property global breaks Flickr and Jira, which probably means that the proposal has to be changed to use a different property name. Discussions about using self or System.global started already.

So let’s hope for the best, because even when we try to avoid the usage of globals, there are use cases for them and these shouldn't need the usage of a heavily nested ternary operator that no one understands.

Load time