7

I came across this rather interesting way to create a JavaScript singleton that can be instantiated with the new keyword, like var x = new SingletonClass(). I have a pretty good grasp of variable scope and closures, etc., but I'm having a hard time understanding exactly why this block of code works the way it does.

// EDIT: DO NOT USE this code; see the answers below
function SingletonClass() {
  this.instance = null;
  var things = [];

  function getInstance() {
    if (!this.instance) {
      this.instance = {
        add: function(thing) {
          things.push(thing);
        },
        list: function() {
          console.log(things.toString());
        }
      };
    }
    return this.instance;
  }

  return getInstance();
}

var obj1 = new SingletonClass();
obj1.add("apple");
obj1.list();  //"apple"

var obj2 = new SingletonClass();
obj2.add("banana");
obj1.list();  //"apple,banana"
obj2.list();  //"apple,banana"

obj1.add("carrot");
obj1.list();  //"apple,banana,carrot"
obj2.list();  //"apple,banana,carrot"

My intuition says that each time a new SingletonClass is instantiated, this refers to that fresh new object -- but then since a totally separate object is returned by the constructor, I would figure this would just get discarded. But it hangs around. How? Why?

There's some tiny little detail going on here that I'm missing. Can anybody shine some light on it?

EDIT: Turns out this code is bad. The reason why it "magically" seems to hold a reference to the instance is because it's actually silently storing it in the global object. It's bad practice at best, and undoubtedly bug-prone.

smitelli
  • 6,835
  • 3
  • 31
  • 53
  • 3
    Where did you find this? It looks broken to me. The "getInstance" function is invoked without an explicit receiver, so `this` will be the global object (`window` in a browser). – Pointy Oct 02 '12 at 02:22
  • 1
    Note that if called in strict mode, `this` in `getInstance` will be undefined, throwing a reference error (or similar) attempting to evaluate `this.instance`. – RobG Oct 02 '12 at 02:38
  • You're right @Pointy, I realize now that it **is** (conceptually) broken. The thought had never crossed my mind that `this` could refer to the global object. – smitelli Oct 02 '12 at 02:39

5 Answers5

3

Don't confused by the this inside the function getInstance, that this is the global object window, so you are creating an object and assigned to the window object, and next time you call the constructor, you are checking whether window.instance is existing.

The code this.instance = null; is meanless, just confusing you. Remove it won't change anything.

The below is from the MDN.

When the code new foo(...) is executed, the following things happen:

  1. A new object is created, inheriting from foo.prototype.
  2. The constructor function foo is called with the specified arguments and this bound to the newly created object. new foo is equivalent to new foo(), i.e. if no argument list is specified, foo is called without arguments.
  3. The object returned by the constructor function becomes the result of the whole new expression. If the constructor function doesn't explicitly return an object, the object created in step 1 is used instead. (Normally constructors don't return a value, but they can choose to do so if they want to override the normal object creation process.)

Note the step3, when you have return statement in constructor, the returned result will be the result of the new expression.

xdazz
  • 158,678
  • 38
  • 247
  • 274
  • Yup, I see that now. I took it for granted that `this` would always mean, well, `this` and I never even thought to check the global object. I'll amend the code with a big DO NOT USE comment in case somebody gets the bright idea to copy it. ;) – smitelli Oct 02 '12 at 02:58
2

When the constructor calls getInstance(), the this pointer inside getInstance() is set to window so this.instance inside of it is a global variable window.instance.

This code just uses a global variable window.instance to keep track of the one instance and could be made a LOT simpler than it is.

A much cleaner way to implement this would be this. The one global instance is stored as a property of the function itself rather than in a top-level global variable.

    function SingletonClass() {
        var things = [];

        // if there is a previous instance, return it
        if (SingletonClass.inst) {
            return SingletonClass.inst;
        }
        // if not called with 'new', force it
        if (!this instanceof SingletonClass) {
            return new SingletonClass();
        }

        // remember the first created instance
        SingletonClass.inst = this;

        // add methods that can see our private `things` variable
        this.add = function(thing) {
           things.push(thing);
        }

        this.list = function() {
            console.log(things.toString());
        }
    }
jfriend00
  • 683,504
  • 96
  • 985
  • 979
1

It's using window - getInstance's this is not the same as SingletonClass's this:

function Singleton() {
    console.log("This is:", this);
    this.instance = null;

    function _get() {
        console.log("_get's this is:", this);
        if (!this.instance) {
           console.log("Instance is null");
           this.instance = { test: 1 };
        }
        return this.instance;
    }

    return _get();
}

var x = new Singleton();

// Output is:
This is: 
Singleton
_get's this is: 
Window
Instance is null

A better way to implement the Singleton pattern in JavaScript might be:

function Singleton() {

    // Handle not being called with `new`
    if (!this instanceof Singleton) {
        return new Singleton();
    }

    var instantiating = !!Singleton.instance,
        things = [];

    Singleton.instance = instantiating ? this : Singleton.instance;

    if (instantiating) {
        this.list = function() {
            console.log(things.toString());
        }

        this.add = function(item) {
            things.push(item);
        }
    }

    return Singleton.instance;
}

This still suffers from limitations. For example, if someone replaces Singleton.instance with another object then tests between different instances of Singleton may break - e.g.:

var x = new Singleton();
// Har, har, we be breaking things!
Singleton.instance = {"not": "the", "same": "at", "all": true};
var y = new Singleton();

console.log(x === y); // false
Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
  • It's a shame I can only accept one answer, because I like this answer too (especially the replacement code). The thing that originally drew me to the code I posted was the fact that it purported to do this without adding a property to the class -- but it turns out it was snake oil, storing it in `window` instead. – smitelli Oct 02 '12 at 03:01
1

The keyword this in the getInstance function indicates a Window object. In the SingletonClass function, it indicates a SingletonClass object.

With lexical scope, the add and list functions always refer to the same things array.

ЯegDwight
  • 24,821
  • 10
  • 45
  • 52
shinya
  • 11
  • 1
0

This seems to be a good link on singletons:

Simplest/Cleanest way to implement singleton in JavaScript?

Here is an example that pertains to what you are doing above:

var myInstance = {
    method1: function () {
        return "Apple";
    },
    method2: function () {
        return "Orange";
    },
    banana: "Banana"
};

myInstance.grape = "Grape";

console.log(myInstance.method1(), myInstance.method2(), myInstance.banana, myInstance.grape);

Here is working link:

http://www.quirkscode.com/flat/forumPosts/singleton/singleton.html

Community
  • 1
  • 1
Xitalogy
  • 1,592
  • 1
  • 14
  • 17