Asked  7 Months ago    Answers:  5   Viewed   57 times

What is the simplest/cleanest way to implement the singleton pattern in JavaScript?

 Answers

72

I think the easiest way is to declare a simple object literal:

var myInstance = {
  method1: function () {
    // ...
  },
  method2: function () {
    // ...
  }
};

If you want private members on your singleton instance, you can do something like this:

var myInstance = (function() {
  var privateVar = '';

  function privateMethod () {
    // ...
  }

  return { // public interface
    publicMethod1: function () {
      // All private members are accessible here
    },
    publicMethod2: function () {
    }
  };
})();

This has been called the module pattern, and it basically allows you to encapsulate private members on an object, by taking advantage of the use of closures.

If you want to prevent the modification of the singleton object, you can freeze it, using the ES5 Object.freeze method.

That will make the object immutable, preventing any modification to the its structure and values.

If you are using ES6, you can represent a singleton using ES Modules very easily, and you can even hold private state by declaring variables at the module scope:

// my-singleton.js
const somePrivateState = []

function privateFn () {
  // ...
}

export default {
  method1() {
    // ...
  },
  method2() {
    // ...
  }
}

Then you can simply import the singleton object to use it:

import myInstance from './my-singleton.js'
// ...
Tuesday, June 1, 2021
 
Farnabaz
answered 7 Months ago
66

Use an enum:

public enum Foo {
    INSTANCE;
}

Joshua Bloch explained this approach in his Effective Java Reloaded talk at Google I/O 2008: link to video. Also see slides 30-32 of his presentation (effective_java_reloaded.pdf):

The Right Way to Implement a Serializable Singleton

public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

Edit: An online portion of "Effective Java" says:

"This approach is functionally equivalent to the public field approach, except that it is more concise, provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks. While this approach has yet to be widely adopted, a single-element enum type is the best way to implement a singleton."

Tuesday, June 1, 2021
 
adjco
answered 7 Months ago
95

If by "won't be executed" you mean "will do nothing when called more than once", you can create a closure:

var something = (function() {
    var executed = false;
    return function() {
        if (!executed) {
            executed = true;
            // do something
        }
    };
})();

something(); // "do something" happens
something(); // nothing happens

In answer to a comment by @Vladloffe (now deleted): With a global variable, other code could reset the value of the "executed" flag (whatever name you pick for it). With a closure, other code has no way to do that, either accidentally or deliberately.

As other answers here point out, several libraries (such as Underscore and Ramda) have a little utility function (typically named once()

  • ) that accepts a function as an argument and returns another function that calls the supplied function exactly once, regardless of how many times the returned function is called. The returned function also caches the value first returned by the supplied function and returns that on subsequent calls.

    However, if you aren't using such a third-party library, but still want such a utility function (rather than the nonce solution I offered above), it's easy enough to implement. The nicest version I've seen is this one posted by David Walsh:

    function once(fn, context) { 
        var result;
        return function() { 
            if (fn) {
                result = fn.apply(context || this, arguments);
                fn = null;
            }
            return result;
        };
    }
    

    I would be inclined to change fn = null; to fn = context = null;. There's no reason for the closure to maintain a reference to context once fn has been called.

  • [*] Be aware, though, that other libraries, such as this Drupal extension to jQuery, may have a function named once() that does something quite different.

    Tuesday, June 1, 2021
     
    davidb
    answered 7 Months ago
    30

    Sets are now available in ES2015 (aka ES6, i.e. ECMAScript 6). ES6 has been the current standard for JavaScript since June 2015.

    ECMAScript 6 has the data structure Set which works for arbitrary values, is fast and handles NaN correctly. -Axel Rauschmayer, Exploring ES6

    First two examples from Axel Rauschmayer's book Exploring ES6:

    Managing single elements:

    > let set = new Set();
    > set.add('red')
    
    > set.has('red')
    true
    > set.delete('red')
    true
    > set.has('red')
    false
    

    Determining the size of a Set and clearing it:

    > let set = new Set();
    > set.add('red')
    > set.add('green')
    
    > set.size
    2
    > set.clear();
    > set.size
    0
    

    I would check out Exploring ES6 if you want to learn more about Sets in JavaScript. The book is free to read online, but if you would like to support the author Dr. Axel Rauschmayer you can purchase the book for around $30.

    If you want to use Sets and ES6 now you can use Babel, the ES6 to ES5 transpiler, and its polyfills.

    Edit: As of June 6th, 2017 most of the major browsers have full Set support in their latest versions (except IE 11). This means you may not need babel if you don't care to support older browsers. If you want to see compatibility in different browsers including your current browser check Kangax's ES6 compatibility table.

    EDIT:

    Just clarification on initialization. Sets can take any synchronous iterable in their constructor. This means they can take not just arrays but also strings, and iterators. Take for example the following array and string initialization of a set:

    const set1 = new Set(['a','a','b','b','c','c']);
    console.log(...set1);
    console.log(set1.size);
    const set2 = new Set("aabbcc");
    console.log(...set2);
    console.log(set2.size);

    Both outputs of the array and string are the same. Note that ...set1 is the spread syntax. It appears that each element of the iterable is added one by one to the set, so since both the array and string have the same elements and since the elements are in the same order the set is created the same. Another thing to note about sets is when iterating over them the iteration order follows the order that the elements were inserted into the set. Here's an example of iterating over a set:

    const set1 = new Set(['a','a','b','b','c','c']);
    for(const element of set1) {
      console.log(element);
    }

    Since you can use any iterable to initialize a set you could even use a iterator from a generator function. Here is two such examples of iterator initializations that produce the same output:

    // a simple generator example
    function* getLetters1 () {
      yield 'a';
      yield 'a';
      yield 'b';
      yield 'b';
      yield 'c';
      yield 'c';
    }
    
    // a somewhat more commonplace generator example
    // with the same output as getLetters1.
    function* getLetters2 (letters, repeatTimes) {
      for(const letter of letters) {
        for(let i = 0; i < repeatTimes; ++i) { 
          yield letter;
        }
      }
    }
    
    console.log("------ getLetters1 ------");
    console.log(...getLetters1());
    const set3 = new Set(getLetters1());
    console.log(...set3);
    console.log(set3.size);
    
    console.log("------ getLetters2 ------");
    console.log(...getLetters2('abc', 2));
    const set4 = new Set(getLetters2('abc', 2));
    console.log(...set4);
    console.log(set4.size);

    These examples' generator functions could just be written to not repeat, but if the generator function is more complicated and as long as the following doesn't impact performance too negatively you could use the Set method to help get only values from a generator that don't repeat.

    If you want to know more about sets without reading Dr. Rauschmayer's chapter of his book you can check out the MDN docs on Set. MDN also has more examples of iterating over a set such as using forEach and using the .keys, .values, and .entries methods. MDN also has examples such as set union, set intersection, set difference, symmetric set difference, and set superset checking. Hopefully most of those operations will become available in JavaScript without needing to build your own functions supporting them. In fact, there is this TC39 proposal for new Set methods which should hopefully add the following methods to Set in JavaScript at some future point in time if the proposal reaches stage 4:

    • Set.prototype.intersection(iterable) - method creates new Set instance by set intersection operation.
    • Set.prototype.union(iterable) - method creates new Set instance by set union operation.
    • Set.prototype.difference(iterable) - method creates new Set without elements present in iterable.
    • Set.prototype.symmetricDifference(iterable) - returns Set of elements found only in either this or in iterable.
    • Set.prototype.isSubsetOf(iterable)
    • Set.prototype.isDisjointFrom(iterable)
    • Set.prototype.isSupersetOf(iterable)
    Saturday, September 4, 2021
     
    Pachvarsh
    answered 3 Months ago
    94

    Yes, in most cases you don't need this complexity, and would just do

    var singleton = {
        someMethod: function() {}
    };
    

    However, the pattern with that getSingleton function does have one advantage: The object is only constructed when the function is called (for the first time), not before the object is actually needed. Depending on the complexity of the object, this can improve memory usage and startup time of your program. It's basically lazy-loading the module.

    Wednesday, September 15, 2021
     
    wael
    answered 3 Months ago
    Only authorized users can answer the question. Please sign in first, or register a free account.
    Not the answer you're looking for? Browse other questions tagged :  
    Share