Jonathan Jones
Jonathan Jones

Reputation: 473

Calling nested functions in Javascript OOP

I am trying to become more familiar with Javascript OOP and so I wrote a little test script but I keep getting an exception when I test it:

Exception:

Uncaught TypeError: Cannot call method 'day' of undefined

Code:

(function () {
    function Time (date) {
        var self = this;
        var timeInWeek = 604800000;
        var timeInDay = 86400000;
        var dateInMilliSeconds = date.getTime();

        self.add = function (num) {
            self.day = function () {
                var newDate = new Date();
                newDate.setTime(dateInMilliSeconds + (timeInDay * num));
                return newDate;
            };
        };
    };
    var date = new Date();
    var time = new Time(date).add(1).day();
    console.log(time);
})();

And when I run the test script outside of the IIFE pattern I am getting the exception Time is undefined, I am new to Javascript OOP so when I tried reading other Javascript libraries a good chunk was over my head. Any help is appreciated.

Upvotes: 1

Views: 131

Answers (2)

tewathia
tewathia

Reputation: 7298

You've missed a return this; (which must be there to allow daisy chaining)

function Time (date) {
        var self = this;
        var timeInWeek = 604800000;
        var timeInDay = 86400000;
        var dateInMilliSeconds = date.getTime();

        self.add = function (num) {
            self.day = function () {
                var newDate = new Date();
                newDate.setTime(dateInMilliSeconds + (timeInDay * num));
                return newDate;
            };
            return this; //this
        };
    };

Without the return statement, in new Time(date).add(1).day()) the day() is being treated as a method of the value returned by the add method of the new Time() instance. If the add method doesn't return an object that has a day method, you're sure to get an error.

The return this is necessary only for chaining. Your code would work fine with

var time = new Time(date);
time.add(1);
time.day();

OR

var time = new Time(date).add(1);
time.day()

Upvotes: 3

user2864740
user2864740

Reputation: 61885

The add method returns undefined as it has no return statement. The problem is unrelated to the use of the IIFE.

Instead, the add method simply added (possibly overwriting) the day method when it was executed so the following would have "worked":

var time = new Time(date)
t.add(1);    // returns undefined, but has a side-effect of adding "day" to t
t.day();     

However, I suspect that the issue is two-fold:

  1. add should return an object of a compatible type for Method Chaining; this can either be same object (for a mutable design) or it can be new object (for an immutable design).

  2. day should be added directly to each Time object; this would make it so new Time(date).day() would work.

For instance:

function Time (date) {
    var self = this;
    var timeInWeek = 604800000;
    var timeInDay = 86400000;
    var dateInMilliSeconds = date.getTime();

    self.add = function (num) {
        // The idea here is to return an object of the same type for "chaining".
        // Here I returned a new Time object (you'll have to work out the details),
        // although for mutable objects, "return self" would be appropriate.
        return new Time(dateInMilliSeconds + (timeInDay * num));
    };
    self.day = function () {
        // actually return the "day", whatever that is.
        return ...;
    };
};

While this is can be a nice exercise, for production code I'd recommend moment.js unless there is as compelling reason otherwise. The source code for moment.js (which might make a good reference) is on github/moment. The "add" (with omissions and additional comments) looks like:

add : function (input, val) {
    // mutate this objects data (but not methods)
    addOrSubtractDurationFromMoment(this, dur, 1);
    // returns the same object for chaining
    return this;
},

Upvotes: 4

Related Questions