Singleton in JavaScript

6

How to implement the Singleton pattern in JavsScript in a simple and correct way - what assures me that the instance will be unique?

I've seen some rather complex implementations, but would not that be enough?

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

What would she have done wrong?

    
asked by anonymous 15.11.2014 / 19:12

2 answers

8

It is 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 Classical OO , where 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)

In Prototypic OO , 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 Classic 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 "class" B inherited from A, and the objects "instantiated the class", right? But the above code could also be spelled 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 that any read access type objeto2.foo will first see if the foo property exists 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 update, if it already exists). You can read more about how prototypes work in that related question .

The consequence of this, however, is that if you have a reference to an object nothing prevents you from creating another one that inherits 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 is immutable , or has "banned extensions" via Object.preventExtensions or Object.seal , none of this prevents it 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. Only by preventing others from getting a reference to him ...

Finally, if it was not clear, creating an "empty" object inheriting from another would be the same as "creating another instance of your class". For all the methods of the first are available to be called, and the new object has a copy of all its properties - being able to change them or not. If any method of the old object assumes that it will always be called using xyz as this , calling it via abc will violate this premise and may have negative consequences. That is, do not assume that a JavaScript object is guaranteed to be singleton .

    
15.11.2014 / 19:49
4

If what you want with a singleton is to have a single instance, it really does not have to complicate too much. But without seeing a whole example, you can not tell if your example code is correct. For example, if xyz is a local variable you will create a new instance the entire 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 (){
        [..]
    })();
}

If xyz is a global variable, it looks strange the way you wrote it. Why check for the presence of a declared xyz instead of direct boot?

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

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

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

Finally, one thing to watch out for: When you are 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.

    
15.11.2014 / 19:58