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.
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.
Share Improve this question edited Oct 2, 2012 at 3:05 smitelli asked Oct 2, 2012 at 2:09 smitellismitelli 7,5854 gold badges34 silver badges57 bronze badges 3-
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 Commented Oct 2, 2012 at 2:22 -
1
Note that if called in strict mode,
this
ingetInstance
will be undefined, throwing a reference error (or similar) attempting to evaluatethis.instance
. – RobG Commented Oct 2, 2012 at 2: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 Commented Oct 2, 2012 at 2:39
5 Answers
Reset to default 3Don'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:
- A new object is created, inheriting from foo.prototype.
- 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.- The object returned by the constructor function bees 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.
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());
}
}
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
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.
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./flat/forumPosts/singleton/singleton.html