Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Object Literal or Modular Javascript Design Pattern

This may have already been asked lots of times, and I've searched around SO but so far all the answers I read aren't exactly what I'm looking for.

I'm working on a website with moderate DOM elements showing/hiding, some AJAX calls, and probably something else. So I'll be having two main script files (HTML5 Boilerplate standard)

plugins.js // third party plugins here
site.js // all my site specific code here

Previously I'm using object literal design pattern, so my site.js is something like this:

var site = {
  version: '0.1',
  init: function() {
    site.registerEvents();
  },
  registerEvents: function() {
    $('.back-to-top').on('click', site.scrollToTop);
  },
  scrollToTop: function() {
    $('body').animate({scrollTop: 0}, 400);
  }
};

$(function() {
  site.init();
});

So far so good, it's nicely readable, all methods are public (I kinda like this, as I can test them via Chrome Dev Tools directly if necessary). However, I intend to decouple some of the site's functionality into more modular style, so I want to have something like this below the code above (or in separate files):

site.auth = {
  init: function() {
    site.auth.doms.loginButton.on('click', site.auth.events.onLoginButtonClicked);
  },
  doms: {
    loginButton: $('.login'),
    registerButton: $('.register')
  },
  events: {
    onLoginButtonClicked: function() {
    }
  },
  fbLogin: function() {
  }
};

site.dashboard = {
};

site.quiz = {
};

// more modules

As you can see, it is very readable. However there is one obvious downside, which is I have to write code like site.auth.doms.loginButton and site.auth.events.onLoginButtonClicked. Suddenly it becomes hard to read, and it will only grow longer the more complex the functionality gets. Then I tried the modular pattern:

var site = (function() {
  function init() {
    $('.back-to-top').on('click', scrollToTop);
    site.auth.init();
  }

  function scrollToTop() {
    $('body').animate({scrollTop: 0}, 400);
  }

  return {
    init: init
  }
})();

site.auth = (function() {
  var doms = {
    loginButton: $('.login'),
    registerButton: $('.register')
  };

  function init() {
    doms.loginButton.on('click', onLoginButtonClicked);
  }

  function onLoginButtonClicked() {

  }

  return {
    init: init
  }
})();

// more modules

As you can see, those long names are gone, but then I guess I have to init all other modules in the site.init() function to construct them all? Then I have to remember to return the functions that need to be accessible by other modules. Both of them are okay I guess albeit a bit of a hassle, but overall, am I onto a better workflow working with modular pattern?

like image 757
Henson Avatar asked Mar 30 '13 12:03

Henson


1 Answers

The correct answer, here, of course, is: "it depends".

If you're totally okay with all data and all methods, for every section of your site being 100% public, then just using a single literal (or multiple literals), with nested objects if desired, is totally fine, assuming that you can keep it from turning into one gigantic ball of code.

If you want any kind of private state, which has any kind of persistence (ie: doesn't reset every time you run a function), then the revealing-module is great.

That said:
It's not a requirement of the revealing-module for you to have an .init method, at all.
If your module can be self-contained, then just focus on exporting what you WOULD like to make public.

To this end, when I'm writing code which a team might look at, later, I'm finding myself creating a public_interface object and returning it (the named version of the anonymous object you return).

The benefit of this is minimal, except to add the understanding that anything which needs to be made public needs to be appended to the interface.

The way you're currently using it:

var module = (function () { /* ... */ return {}; }());

module.submodule = (function () { /*...*/ return {}; }());

Is no better or worse than literals, because you can just as easily do this:

var module = {
    a : "",
    method : function () {},
    meta : { }
};

module.submodule = {
    a : "",
    method : function () {},
    meta : { }
};

Until you hit something which doesn't work for you, work with what fills your needs.

Personally, I'll typically build any data-only objects as literals: config-objects, objects which come in from other connections, etc...

Any dirt-simple object which requires maybe one or two methods, and can be built by nesting only one or two levels deep, I might build literally, as well (as long as it doesn't require initialization).

// ex:
var rectangle = {
    width  : 12,
    height : 24,
    area : 0,
    perimeter : 0,
    init_area : function () { this.area = this.width * this.height; return this; }, // buh...
    init_perimeter : function () { this.perimeter = (this.width * 2) + (this.height * 2); return this; } // double-buh...
}.init_area().init_perimeter();

If I needed several of these, perhaps I'd make a constructor.
But if I only ever needed one of something unique like this, wouldn't it save me some headaches to just do something like this:

var rectangle = (function (width, height) {
    var public_interface = {
        width  : width,
        height : height,
        area   : width * height,
        perimeter : (2 * width) + (2 * height)
    };
    return public_interface;
}(12, 24));

If there were more-advanced calculations required, I could keep any extra vars private, and work on them from the inside.
If I needed to have sensitive data inside of an object, and functions to work on that data, then I could have public-functions which call those private functions, and return results, rather than providing access.

Also, if I refactor my code, and decide to rename rectangle at some point, then any functions nested 3 or more deep, which are referring to rectangle will have to be modified, as well.
Again, if you're structuring your methods so that they don't need to directly ask for any object which is further up than this, then you won't have this problem...

...but if you have an interface that looks like:

MyApp.myServices.webService.send();

and it's expecting to find:

MyApp.appData.user.tokens.latest;  // where personally, I might leave tokens in a closure

If you change the structure of your appData module, you're going to have all kinds of errors in your webService module, until you find every reference to the old format, and rename them all.

like image 117
Norguard Avatar answered Nov 13 '22 15:11

Norguard