It worked yesterday.
It worked yesterday.

Reputation: 4617

Using self invoking anonymous functions

I am trying to build a shopping cart for learning purpose. I have following codes

HTML

<div id="MyCart" class="product-cart">
    <ul>
        <li class="item"></li>
        <li class="item"></li>
        <li class="item"></li>
    </ul>
</div>

js

var cart = (function () {

    cart.createCart = function (cartId) {
        console.log(cartId);
        cartId = document.getElementById(cartId);
    }   


    return cart;
}());

var shoopingCart = cart.createCart("MyCart");

But this code throws following error

Uncaught TypeError: Cannot set property 'createCart' of undefined

After spending few hours in internet and following some tutorials I did following changes to the code and then it started working.

But still I dont understand what i have done here

var cart = (function (cart) {

    cart.createCart = function (cartId) {
        console.log(cartId);
        cartId = document.getElementById(cartId);
    }   


    return cart;
}(cart || {}));

var shoopingCart = cart.createCart("MyCart");

Can some one please explain me why the code started to work after passing cart || {} expression into the anonymous function? Some detailed explanation would be great. :)

Upvotes: 7

Views: 1544

Answers (4)

Callum Linington
Callum Linington

Reputation: 14417

So without the variable passed into the scope.

var cart = (function (cart) {

    // can't add a property or method onto undefined.
    cart.createCart = function (cartId) {
        console.log(cartId);
        cartId = document.getElementById(cartId);
    }   


    return cart;
}()); // without any value here ^ cart will be undefined.

var shoopingCart = cart.createCart("MyCart");

However, if you pass the variable to the context:

var cart = (function (cart) {

    // cart now is an object which you can attach a property or method
    cart.createCart = function (cartId) {
        console.log(cartId);
        cartId = document.getElementById(cartId);
    }   


    return cart;
}(cart || {})); // pass in cart, or if it is null a new object {}

var shoopingCart = cart.createCart("MyCart");

So an IIFE looks like this:

(function () { })();

so ignoring the function you get ( )(); in that second pair of parenthesis you pass the parameters to the function in the first set. This is because IIFE creates a whole new clean scope. This is why we use IIFE because it can isolate global variables that we use in them.

so if you have this:

<script>

var someGlobalVariable = "hey";

(function () {

   // using someGlobalVariable here will be fine

   var myIIFEScopedVariable = "ho"; 

})();

// trying to access myIIFEScopedVariable here will fail because again, it hasn't been defined here.

</script>

So IIFE's are great for controlling what you have in scopes.

The cart || {} is a JavaScript null coalesce, so it says, pass in cart but if it is null give it an empty object

Upvotes: 4

Marcos P&#233;rez Gude
Marcos P&#233;rez Gude

Reputation: 22158

With this code:

(cart || {})

You are passing the scope of the function and if it is empty, you pass an empty object ({}). It seems like you have a cart object, and when you make var cart = (function .... you are overriding it.

Maybe your object must be called Cart with uppercase and it works too:

  var cart = (function() {
       Cart.createCart()...
  });

Upvotes: 1

kieranpotts
kieranpotts

Reputation: 1606

I haven't run this code, but from reading through it:

In your first example, on line three, where you call cart.createCart, the object cart does not yet exist. The function has not yet returned the object that it itself refers to internally!

In the second example, the cart object still does not exist, but you have provided a fallback to a new blank object:

cart || {}

So, inside the function, cart is a new blank object, to which you apply the createCart property.

EDIT: Erik Lundgren's alternative function just posted is what you need. It's meaning is clearer, as the cart object is clearly instantiated inside the function. Use that! :)

The only thing that I would add is that you don't really have any reason to do all this inside an immediately invoked function expression, in this particular case. There is nothing in there that needs scoping. So this will do:

var cart = {};
cart.createCart = function (cartId) {
    console.log(cartId);
    cartId = document.getElementById(cartId);
}   

var shoopingCart = cart.createCart("MyCart");

Upvotes: 2

Because you are in progress of declaring the cart variable it is not instantiated yet. When you instead pass in an empty object (which is what cart || {} will evaluate to it will then add the method to that object, return it and cart will then be that object. What the second function does is essentially the same as the following code:

var cart = (function () {
    var cart = {};
    cart.createCart = function (cartId) {
        console.log(cartId);
        cartId = document.getElementById(cartId);
    }   


    return cart;
}());

var shoopingCart = cart.createCart("MyCart");

Upvotes: 2

Related Questions