Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Annotate Singleton objects in JavaScript for the Google Closure Compiler, or "dangerous use of the global this object" warning

I'm working with the Google Closure Compiler in ADVANCED_OPTIMIZATIONS compilation level and have started to annotate my constructors because I get all kinds of warnings:

WARNING - dangerous use of the global this object

For my 'constructor' type functions I'll annotate them like this:

/**
 * Foo is my constructor
 * @constructor
 */
Foo = function() {
   this.member = {};
}

/**
 * does something
 * @this {Foo}
 */
Foo.prototype.doSomething = function() {
   ...
}

That seems to work fine, however what if I have a 'singleton' object that isn't constructed with var myFoo = new Foo(); I couldn't find in the documentation how to annotate this type of object because its type is just object right?

Bar = {
   member: null,
   init: function() {
      this.member = {};
   }
};
like image 351
Matt Palmerlee Avatar asked Dec 10 '22 09:12

Matt Palmerlee


2 Answers

The preferred way of creating singletons in Closure is like this:

/** @constructor */
var Bar = function() { };
goog.addSingletonGetter(Bar);

Bar.prototype.member = null;

Bar.prototype.init = function() {
  this.member = {};
};

This allows for lazy instantiation of the singleton. Use it like this:

var bar1 = Bar.getInstance();
var bar2 = Bar.getInstance();

bar1.init();
console.log(bar2.member);

Keep in mind that this doesn't prevent people from using the constructor to create instances of Bar.

like image 172
Jan Avatar answered Dec 25 '22 22:12

Jan


This is exactly the type of potential bug that "dangerous use of this" warns you against. In your example, the Closure Compiler may try to "flatten" your code to:

Bar$member = null;
Bar$init = function() { this.member = {}; };

NOTE: The Closure Compiler currently will not flatten a namespace that is declared as a global object (i.e. without the "var" keyword in front), so your code may still work now. However, there is no telling that it won't do that in a future version and your code will suddenly break without warning.

Of course, then "Bar$member" and "Bar$init" will be renamed to "a" and "b" respectively. This is called "namespace flattening" or "collapsing of properties".

You can immediately see that your code no longer works correctly. Before compilation, if you write:

Bar.init();

this will refer to Bar. However, after compilation it becomes:

Bar$init();

this will no longer refer to Bar. Instead it refers to the global object.

This is way the compiler is trying to warn you that using "this" in such a way is "dangerous", because "this" may be changed to refer to the "global" object. That's the true meaning of the warning.

In short, DO NOT DO THIS. This type of coding style creates bugs that are very difficult to track down.

Modify your code this way:

var Bar = {    // Closure Compiler treats globals and properties on global differently
  member: null,
  init: function() { Bar.member = {}; }
};

or use a closure:

var Bar = (function() {
  var member = null;
  return {
    init: function() { member = {}; }
  };
})();

When using the Closure Compiler in Advanced Mode, do not try to get rid of warnings by annotating them away. Warnings are there for a reason -- they try to warn you about something.

like image 32
Stephen Chung Avatar answered Dec 26 '22 00:12

Stephen Chung