Where Marketing, Analytics, and UX meet

JavaScript: The why & how of singletons

JavaScript client-side tutorial series image

JavaScript is incredibly powerful but it doesn't follow the rules of a lot of different languages. For example, languages like C# contain the concept of defining classes within a namespace as "internal". This is important when we intend to have one Object create another Object and we want to be sure there is no other way to create that Object. This post will discuss how to create internal-type classes in the singleton pattern.

This post is a part of a series! Check out the JavaScript Client-Side Tutorial Series

Why would we want to manage the creation of an Object

If we were creating an Object to hold Google search results, we would create an object that could possible hold an array of results and additionally contain methods to refresh the result. In an Object like this, we might not need to manage the creation of such an Object. Said another way, we could create as many instances with difference results as we want to basically cache the results of searches the page user is making.

But there are time when we might want to have an Object that represents the state and properties of the user of the page.  And no matter what they are trying to do on the page, we don't want to have to pass that Object around to all of the functionality of the page. But at the same time we might need every instance of the user management Object to stay in sync in it's state and property data. If the page we are on is a large single-page JavaScript application, and the user has updated their username in the profile section of the app, then a second later over in the comment section of the app, the user management Object in that area of the app should know that the username has been updated and add that updated username to the comment.

Managing a single state and property of an Object across multiple instances of that same Object is done using the Singleton pattern. Here is how that works.

A Singleton pattern example: The CurrentUserSingleton Object

We want our actual Object that manages the details of the CurrentUser to be a singleton Object. So we should create a CurrentUserSingleton Object. Let's take a look at what properties and methods should exist for our CurrentUserSingleton Object.

uml diagram for the CurrentUserSingleton Object

We are going to create four methods: one to set and get the firstName of the user, one to set and get the LastName for the user, one to get the fullName for the user, as well as one to get the id. There are no public properties on our object. All of the properties are internal-only and private.

A Singleton pattern example: The CurrentUser singleton manager Object

To manage the creation of our CurrentUserSingleton Object such that all instances reference the same single instance in the memory of the page, we are going to need an object to manage the creation of our CurrentUserSingleton instances. Here is what that class should look like:

uml diagram for the CurrentUser singleton manager Object

The CurrentUser class has only one private property of type CurrentUserSingleton, which will hold the only real single instance of our CurrentUserSingleton Object. It also has a only one method, the getSingleton public method, which will be used to hand back a reference to that private property instance.

The relationship between singleton and singleton manager Objects

Now that we have defined our objects, it is important to understand the scope, visibility and relationship of these Objects. Let's take a look at how we would diagram that relationship:

uml diagram for the CurrentUser and CurrentUserSingleton relationship

The notation here depicts that we could make use of the CurrentUser Object, but the CurrentUserSingleton is a subordinate internal Object only available by direct reference to the CurrentUser Object. Said another way, we cannot directly create a new CurrentUserSingleton Object, but we could request it from the CurrentUser Object. This is how we are certain to keep all of the instances of the CurrentUserSingleton Object synced.

Inevitably, we will do exactly that: ask the CurrentUser Object to get us a copy of/reference to the CurrentUserSingleton Object using the getSingleton method of the CurrentUser Object. Let's have a look at the code that creates these two objects and then the code that makes use of them.

The code

If you've followed along on the other JavaScript posts in this series, you should be ready for the code below (which we will unpack, bit by bit, in a moment):

(function(){
  
  (function(){
        
        // UserAccount Object singleton: start
        //private properties and methods
        var _fullName = 'Need to set the first and last name first';
        var _firstName = 'You need to set the first name';
        var _lastName = 'You need to set the last name';
        var _id = '';

        singletonInstance = new function(){

          //public properties and methods
          _id = 0001;
          
          this.firstName = function( thisFirstName ){
            _firstName = thisFirstName;
            if( _firstName.length > 0 && _lastName.length > 0){
              _fullName = _firstName + ' ' + _lastName;
            }
            return _firstName;
          }
          
          this.lastName = function( thisLastName ){
            _lastName = thisLastName;
            if( _firstName.length > 0 && _lastName.length > 0){
              _fullName = _firstName + ' ' + _lastName;
            }
            return _lastName;
          }
          
          this.fullName = function(){
            return _fullName;
          }
          
          this.id = function(){
            return _id
          }

        }
        // UserAccount Object singleton: end  
  })();
  
  // singleton manager: start
  var singletonInstance;
  
  CurrentUser = new function(){   
    
    this.getSingleton = function(){
      return singletonInstance;
    }
    
  }
  // singleton manager: end
 })();
  
 var objCurrentUser = CurrentUser.getSingleton();
 objCurrentUser.firstName('Andrew');
 console.log( objCurrentUser.fullName() );
 objCurrentUser.lastName('Garfield');
 console.log( objCurrentUser.fullName() );
 console.log( objCurrentUser.id() );
  
 var objCurrentUserCopy = CurrentUser.getSingleton();
 console.log( objCurrentUserCopy.fullName() ); 
 objCurrentUserCopy.firstName('Tom');
 objCurrentUserCopy.lastName('Holland');
  
 console.log( objCurrentUser.fullName() );

First, let's have a look at the creation of the singleton manager Object, the CurrentUser:

(function(){
  
  // singleton manager: start
  
  // private
  var singletonInstance;
  
  CurrentUser = new function(){   
    
    // public
    this.getSingleton = function(){
      return singletonInstance;
    }
    
  }
  // singleton manager: end

 })();

You can see within our closure the creation of our global CurrentUser Object, which will be our singleton manager. This Object has one private property, singletonInstance, which will hold the CurrentUserSingleton Object instance. Next, you can see that the Object has one public method, getSingleton, which returns a reference to that private singletonInstance property. That is all that the manager does. It is pretty simple.

If you tried to directly reference or create a copy of the singletonInstance, you would not be able to do it. It is private to the CurrentUser Object and cannot be seen by other code.

Now let's look at how we create the CurrentUserSingleton Object inside and as a child of the CurrentUser Object:

(function(){
  
  (function(){
        
        // UserAccount Object singleton: start
        //private properties and methods
        var _fullName = 'Need to set the first and last name first';
        var _firstName = 'You need to set the first name';
        var _lastName = 'You need to set the last name';
        var _id = '';

        singletonInstance = new function(){

          //public properties and methods
          _id = 0001;
          
          this.firstName = function( thisFirstName ){
            _firstName = thisFirstName;
            if( _firstName.length > 0 && _lastName.length > 0){
              _fullName = _firstName + ' ' + _lastName;
            }
            return _firstName;
          }
          
          this.lastName = function( thisLastName ){
            _lastName = thisLastName;
            if( _firstName.length > 0 && _lastName.length > 0){
              _fullName = _firstName + ' ' + _lastName;
            }
            return _lastName;
          }
          
          this.fullName = function(){
            return _fullName;
          }
          
          this.id = function(){
            return _id
          }

        }
        // UserAccount Object singleton: end  
  })();
  
  // singleton manager: start
  // private
  var singletonInstance;
  
  CurrentUser = new function(){   
    // public
    this.getSingleton = function(){
      return singletonInstance;
    }
    
  }
  // singleton manager: end
 })();

This bit is a tiny bit tricky. First remember that we are creating the CurrentUser Object instantly when the page loads, because it is inside the self-executing anonymous function within a closure. the CurrentUser Object is created without the "var" reserved word, so that Object will be created in the global memory space. 

Within that closure, we can then see the start of another closure with a self-executing anonymous function. Inside that closure we are creating a new Object for the singletonInstance property. Looking much lower in the code, you might remember that we defined that variable as a private property of our CurrentUser singleton manager Object. So the inner closure is what is creating the object immediately for that private property. 

You might be wondering why we would bother creating a closure to populate that singletonInstance property. From our previous posts in this tutorial, you might recall that closures help facilitate the possibility of private Object properties and methods. Within a closure you can create Objects for another scope level, like an outer closure in nested closures or for the global memory scope. If you "var" properties or define functions within the closure but outside of the Object you are creating, those properties and methods are only visible to the Objects created inside the closure. So in the case of our CurrentUserSingleton Object, we said we wanted four private properties, and this is how you make that happen.

As a side note, and this is an interesting quirk of the JavaScript language, if you were to create two Objects within a closure and attempt to create a set of private properties and methods, both of those objects would be able to see the private properties and methods. And in the case of properties, not only would that be able to reference those properties, but if one object changed the value of a private property, it would change within the instances of other objects created using the singleton pattern. This, however, is not generally desirable. So this is why we are using a closure within a closure: So that we can isolate one object's private properties and methods from another. 

Finally, let's take a look at the tests for this code:

 var objCurrentUser = CurrentUser.getSingleton();
 objCurrentUser.firstName('Andrew');
 console.log( objCurrentUser.fullName() );
 objCurrentUser.lastName('Garfield');
 console.log( objCurrentUser.fullName() );
 console.log( objCurrentUser.id() );
  
 var objCurrentUserCopy = CurrentUser.getSingleton();
 console.log( objCurrentUserCopy.fullName() ); 
 objCurrentUserCopy.firstName('Tom');
 objCurrentUserCopy.lastName('Holland');
  
 console.log( objCurrentUser.fullName() );

With all of that code happening, the only Object we publicly created was the CurrentUser singleton manager Object. In the above code, we are creating a local variable called objCurrentUser, and we are requesting a reference to our singleton Object from the CurrentUser Object using the getSingleton method.

At this point our objCurrentUser is able to reference the CurrentUserSingleton Object we created inside the CurrentUser. You can see that the code attempts to change the firstName and lastName of the referenced Object and return the fullName and id private and computed properties. If we try to return the fullName value before the firstName and lastName properties are set, we instead get a message telling us we need to set those properties first to compute the fullName. This all works as expected.

Next up we try to get another reference to the same managed CurrentUserSingleton managed Object. This reference gets put into the objCurrentUserCopy Object. If we immediately ask for the fullName property from this new Object, we get it. This is because we are sharing data with all other instances of the CurrentUserSingleton Object in the singleton pattern. 

And finally, if we change the firstName and lastName properties on the objCurrentUserCopy Object, and attempt to return the fullName of the objCurrentUser instance Object, we get the new fullName due to the changes we previously made on the objCurrentUserCopy.

This is it for creating Objects using the singleton pattern. It is a fairly powerful tool for sharing Object within your code without having to explicitly pass those Objects back and forth and into and out of other Objects.

Add new comment

*

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
By submitting this form, you accept the Mollom privacy policy.