Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IIFE creation pattern - but how to support constructor parameters?

Tags:

javascript

Being a longtime classical inheritance OO programmer I'm very comfortable with the use of constructors and creating objects that accept required parameters as constructor arguments. For example, an object that sends alerts related to an order might look like:

var orderNotifier = function(orderId, notifier, recipients)
{
    this.notifyApproved = function()
    { 
        // use the notifier object passed as ctor param to send notifications 
        // related to orderId to recipients 
    }
    this.notifySomeOtherEvent = function() { // use the ctor params again }
}

//  then use it like
var on = new orderNotifier(12345, new BasicNotifier(), someArrayOfEmails);
on.notifyApproved();

So that is a contrived example but exemplifies (IMHO) the value of a parameterized constructor. Specifically:

  1. Passing state to the object in a single statement
  2. Simplified method signatures
  3. Internal state is hidden

Appreciating that the constructor creation pattern in JavaScript doesn't very well support information hiding, I was attracted to the Immediately Invoked Function Expressions (IIFE) pattern with its closures and stronger access control. Now I've run up against the fact that I can't pass object construction parameters using the IIFE pattern, or at least I don't understand how I can.

I know you can pass parameters to the anonymous function like so:

(function(param){})(someVar);

But that isn't the same as explicitly creating a new object and passing parameters to the constructor. By the very nature of the IIFE pattern I don't necessarily have the data to pass to the object yet.

My IIFE version of the above would like like:

var orderNotifier = (function()
{
    var privateHelperMethod = function() { return 'blahblahblah'; };

    return {
        notifyApproved: function(orderId, notifier, recipient)
        { 
            // use the notifier object passed as ctor param to send notifications 
            // related to orderId to recipients
            var msg = privateHelperMethod() 
        },

        notifySomeOtherEvent: function(orderId, notifier, recipient)
        {
            // use the ctor params again
            var msg = privateHelperMethod();
        }
    };
}}();

So I must ask experienced, progressive JavaScript pros: Using standard ECMAScript 5 language features, what is the best practice (or maybe even just a common practice) of creating an object with the IIFE pattern and providing object state in a single operation? (Other than exposing a setState() or similar method.) In other words: How can I have my cake and eat it too?

like image 843
scubasteve Avatar asked Oct 21 '14 22:10

scubasteve


2 Answers

Your IIFE constructor pattern creates a singleton (only one object, ever). It doesn't expose a constructor so it can't be used to create more objects. As such, you can pass any info you want into the singleton via the arguments to the IIFE or just code those arguments right into the implementation.

This is why I wanted you to show us what design pattern you were actually talking about because this particular pattern isn't a general purpose constructor and isn't designed to create multiple objects. It creates a singleton object. That's what it is best for. If the returned object exposed functions that could be used as constructors, then they could take arguments just like other constructors.

I don't see a problem here. I don't see that there is an issue to be solved.

Per your comments, if you want to pass non-static data into the IIFE, then you either have to locate the IIFE after the creation of the non-static data (so that data is available before the IIFE runs), select a different design pattern (e.g. use a traditional constructor) or use an IIFE that creates a constructor (which you can call later), rather than an object.


For example, here's an IIFE that creates a constructor:

var OrderNotifier = (function()
{
    //  private stuff would go here
    //  shared by all instances


    return function(/* constructor args go here */) {

        // per-instance private vars here

        return {
            notifyApproved: function(orderId, notifier, recipient)
            { 
                // use the notifier object passed as ctor param to send notifications 
                // related to orderId to recipients 
            },

            notifySomeOtherEvent: function(orderId, notifier, recipient)
            {
                // use the ctor params again
            }
        };
    }

}}();

// sometime later in your code
var notifier = new OrderNotifier(/* args here */);
like image 53
jfriend00 Avatar answered Oct 15 '22 09:10

jfriend00


The two options you provide essentially have two different uses (and also, the first method is best used with a prototype).

Prototypal object creation

function OrderNotifier(orderId, notifier, recipients)
{
 this.orderId = orderId;
 this.notifier = notifier;
 this.recipients = recipients;
}
OrderNotifier.prototype.notifyApproved = function()
{ 
    // use the notifier object passed as ctor param to send notifications
    // in the form of this.notifier
    // related to orderId to recipients 
    // accessed through this.orderId and this.recipients
}
OrderNotifier.prototype.notifySomeOtherEvent = function() { 
    // use the ctor params again 
}

and used

var on = new OrderNotifier(12345, new BasicNotifier(), someArrayOfEmails);
on.notifyApproved();

This approach is basically for creating objects which are going to have their own unique values but share the same configuration and prototype (behaviors). Using an IIFE (Immediately Invoked Function Expression) is really for a different operation. It can be useful for protecting the global namespace from variable pollution. If there is some work that needs to be done, but the variables shouldn't stick around, then that is where IIFE's shine. In addition, they are very useful for closing over values. However, an IIFE generally runs once and is gone, and as a result it does not need instantiation. Some variables can be passed in if they are to be used or closed over in the function, but in general, the entire IIFE is gone once executed (although a common pattern is to have it modify global state for a library - jQuery does this).


Edit

In your late edit, you create a closure for "private stuff would go here", but it is too abstract of an example to really highlight any benefit. Private stuff could go there, but really, it would be static and could simply have been moved into notifyApproved or notifySomeOtherEvent. It is really a misuse of the IIFE because this could simply be

var orderNotifier = {
 notifyApproved: function(orderId,notifier,recipient){
  //store "private" information here
 },
 notifySomeOtherEvent : function(orderId,notifier,recipient){
  //store "private" information here
 }
};

It would be possible for you to go back, and edit in the IIFE to accept values on load to be passed in and then stored as "private", but the only benefit of doing that would be to close over the values at the time that the script is first executing.

like image 34
Travis J Avatar answered Oct 15 '22 07:10

Travis J