Singleton in Javascript

Asked

Viewed 348 times

8

How to implement the Singleton standard in Javascript in a simple and correct way - which assures me that the instance will be unique?

I’ve seen somewhat complex implementations, but it wouldn’t be enough?

var xyz = xyz || (function (){
    [..]
})();

What would have been wrong with her?

  • I’m not sure what you’re looking for (I think it’s because of the example; usually, Singleton simulacrats in JS use new). I don’t know if it helps, but I usually recommend using a literal object instead of trying to simulate singletons. Solves some use cases, probably not all.

  • Poise, I forgot to comment on this in question. If with the global literal objects, I have need of Singleton.

2 answers

12


It’s impossible to create a Singleton in Javascript, given the prototypical nature of the language. The vast majority of object-oriented languages use what we call OO Classical, in which class inherits from class and object instance class:

classe A   <==   classe B (herda de A - sua "superclasse")
   ^                ^
   |                |
objeto 1         objeto 2 (não herda de objeto 2 - não diretamente, pelo menos)

Already in the O Prototypic, there are no classes, and object inherits from object:

objeto 1   <==   objeto 2 (herda de objeto 1 - seu "protótipo")

Javascript (in its current version) has prototype semantics only, but a bizarre syntax that makes seem that it implements Classical OO, when in fact this does not occur:

function A() { this.foo = 10; }
var objeto1 = new A();

function B() { this.bar = 20; }
B.prototype = new A(); // Vou chamar esse objeto de "anônimo"; poderia ser o próprio objeto1
var objeto2 = new B();

It seems that the "class" B inherited from A, and the objects "instantiated the class", right? But the above code could also be written as follows:

var objeto1 = { foo:10 };
var objeto2 = Object.create({ foo:10 }, { bar:{ value:20 } }); // anônimo poderia ser objeto1

In both cases, anônimo is the prototype of objeto2. This means any read type access objeto2.foo will first see if there is the property foo in objeto2 and, if it does not exist, it will return anônimo.foo. Already in a script, it will create this property in objeto2 if it does not exist (or will update if it already exists). You can read more about how prototypes work in this related question.

The consequence of this, however, is that if you have a reference to one object nothing prevents you from creating another that inherits [prototypically] from it:

var xyz = xyz || (function (){ // Essa função só será chamada uma única vez, garantidamente
    this.foo = function() { ... };
})();

var abc = Object.create(xyz, { bar:{ value:20 } }); // Mas agora abc herda de xyz
abc.foo(); // Chamou o método foo de xyz, usando abc como this!

Although xyz be it immutable, or has "prohibited extensions" via Object.preventExtensions or Object.seal, None of that prevents him from being inherited. If there is a way to mark an object as "it is forbidden to use it as a prototype of other objects", I do not know. Just stopping others from getting a reference for him...

Finally, if it was not clear, creating an "empty" object inherited from another would be the same as "creating another instance of your class". Because all the methods of the first are available to be called, and the new object has a copy of all its properties - it can change them or not. If any method of the ancient object assumes that it will always be called using xyz as this, call him via abc will violate this premise and may have negative consequences. That is: do not assume that a Javascript object is guaranteed Singleton.

4

If what you want with a Singleton is to have a single instance, it doesn’t really need to complicate much. But without seeing an entire example, you can’t tell if your sample code is correct. For example, if xyz is a local variable you will create a new instance all the time.

function getSingleton(){
    //esse código sempre cria uma instância nova
    // é como se você tivesse escrito "var xyz = undefined || (function..."
    var xyz = xyz || (function (){
        [..]
    })();
}

It has already been xyz is a global variable, it is strange the way you wrote it. Why check the presence of a xyz declared earlier rather than direct initialize?

var xyz = (function (){ ... }())

In short, if the goal is to have a single instance lazily initialized, I think the normal would be xyz be declared in a different scope from where it is initialized:

var xyz = null;
function getSingleton(){
    xyz = xyz || (function(){ ... }())
    return xyz
}

Finally, one thing to pay attention to: when you’re writing code that tests whether a global variable exists use window.xyz or typeof xyz !== "undefined" because trying to directly access a global variable that does not exist is a Referenceerror.

  • 1

    I’m not sure, but I believe the motivation behind var x = x || ... is to prevent two or more scripts from creating the same variable. I see this a lot in jQuery source code, for example. This code assumes, of course, that both scripts already have in consensus what should be the value of the variable if it is absent (if one expects one thing and the other expects another, there will be problem...)

  • Yes, var x = x || ... exists, but I’ve never seen anyone use it to make a Singleton. Normally for this you want a single multiple responsible for creating the instance instead of a bunch of modules testing if it already has value...

  • 1

    I agree, I referred to the fact that the instantiation function is only executed once (which actually occurs, although it does not fully characterize the pattern Singleton). As for having a single module responsible, this is desirable yes, but difficult to achieve [guaranteed] in practice (if you are not careful, it ends in a case of Dependency Hell). If your page has 10 external scripts, and you’re not sure if two of them have included the same common dependency, the above standard helps with "in-depth defense"... : P

  • Exactly, @mgibsonbr. That’s the point I wanted to make with the example var x = x || ....

Browser other questions tagged

You are not signed in. Login or sign up in order to post.