5

I've been trying to get to grips with OO JavaScript and created a simple example.

function BasePage(name) {
    this.init(name);
}

BasePage.prototype = {
    init: function(name) {
       this.name = name; 
    },
    getName: function() {
        return this.name;
    }
}

function FaqPage (name, faq) {
    this.init(name, faq);
}

FaqPage.prototype = new BasePage();

FaqPage.prototype = {
    init: function(name, faq) {
        BasePage.prototype.init.call(this, name);
        this.faq = faq; 
    },
    getFaq: function() {
        return this.faq;
    }
}

var faqPage = new FaqPage('Faq Page', 'Faq');

var text = faqPage.getName() + ' ' + faqPage.getFaq();
$('body').text(text);

The result of running this results in the following message:

Uncaught TypeError: Object #<Object> has no method 'getName'

What I would like to know is how can I call the method getName() in the super class without having to override and call it in the sub class?

Also if my if you think this approach is bad/good.

Ryan B
  • 3,364
  • 21
  • 35
Stokedout
  • 11,003
  • 5
  • 24
  • 30

4 Answers4

6

That error occurs because even though you've set FaqPage's prototype to be an instance of BasePage, your next line immediately overwrites that. So FaqPage is not inheriting from BasePage. Add properties/methods to FaqPage's prototype instead of defining it a second time:

FaqPage.prototype.init = function(name, faq) {
    BasePage.prototype.init.call(this, name);
    this.faq = faq; 
}
FaqPage.prototype.getFaq = function() {
    return this.faq;
}
Dan
  • 59,490
  • 13
  • 101
  • 110
  • Thanks, I had a feeling that would be the reason. Is this the only way/recommended way to add methods in OO JS? For me coming from C# it seems ugly not being able to group class methods in one {scope} – Stokedout May 19 '13 at 16:08
  • I know what you mean but yes this is the recommended way. It may grow on you. Keep in mind you can place methods on `BasePage` as well. – Dan May 19 '13 at 16:10
2

The first line is right, you create a prototype from the base object.

FaqPage.prototype = new BasePage();

But then you override it, so is does not inherit the base object anymore

FaqPage.prototype = {
    init: function(name, faq) {
        BasePage.prototype.init.call(this, name);
        this.faq = faq; 
    },
    getFaq: function() {
        return this.faq;
    }
}

Instead of defining the entire prototype, override just individual functions:

FaqPage.prototype.init = function(name, faq) {
    BasePage.prototype.init.call(this, name);
    this.faq = faq; 
};

FaqPage.prototype.getFaq = function() {
    return this.faq;
};
claustrofob
  • 5,448
  • 2
  • 19
  • 22
1

If inheritance is what you are after, it's not very straightforward to accomplish in JavaScript as far as I know. I've used this snippet by John Resig and it works like a charm. http://ejohn.org/blog/simple-javascript-inheritance/

In your case this is how it would work,

BasePage

var BasePage = Class.extend({
  init: function(name){
     this.name = name;
  },
 getName: function(){
     return this.name;
 }
});

FaqPage class inheriting BasePage

var FaqPage = BasePage.extend({
  init: function(name, faq){
    this._super( name );
    this.faq = faq;
  },
  getFaq: function() {
    return this.faq;
  }
});

And then

var faqPage = new FaqPage('Faq Page', 'Faq');
var text = faqPage.getName() + ' ' + faqPage.getFaq();
$('body').text(text);

Of course, for this to work you'll have to include the implementation code on the page I linked.

Here's the fiddle, http://jsfiddle.net/c8yjY/

pseudoh
  • 161
  • 4
  • John Resig's code is a tad bit outdated. There are lots of better OOP libraries out there: http://jsperf.com/oop-benchmark/127 Please read my answer above. – Aadit M Shah May 19 '13 at 17:04
1

I feel your pain. As others have mentioned getName is undefined because you override the prototype of FaqPage. Hence I'll not reiterate that explanation.

That being said I do agree that it's good to encapsulate prototype methods in a single scope. Perhaps you should use a JavaScript library for this purpose. The smallest one out there is augment. In fact it's only 17 lines long:

Function.prototype.augment = function (body) {
    var base = this.prototype;
    var prototype = Object.create(base);
    body.apply(prototype, Array.from(arguments, 1).concat(base));
    if (!Object.ownPropertyOf(prototype, "constructor")) return prototype;
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
};

(function funct() {
    var bind = funct.bind;
    var bindable = Function.bindable = bind.bind(bind);
    var callable = Function.callable = bindable(funct.call);
    Object.ownPropertyOf = callable(funct.hasOwnProperty);
    Array.from = callable([].slice);
}());

Here's how your code would look if you used augment:

var BasePage = Object.augment(function () {
    this.constructor = function (name) {
        this.name = name;
    };

    this.getName = function () {
        return this.name;
    };
});

var FaqPage = BasePage.augment(function (base) {
    this.constructor = function (name, faq) {
        base.constructor.call(this, name);
        this.faq = faq;
    };

    this.getFaq = function () {
        return this.faq;
    };
});

Then you may use it as you normally would:

var faqPage = new FaqPage("Faq Page", "Faq");
var text = faqPage.getName() + " " + faqPage.getFaq();
$("body").text(text);

Hope that helps.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • Thanks Aadit, I see Augment faired well in the benchmark you did, but what happened in Chrome? Graph showed zero operations? – Stokedout May 20 '13 at 07:06
  • @Stokedout - Benchmarks should be taken with a grain of salt. No doubt `augment` is fast. However most JavaScript programmers are amateurs who write sloppy code. To compensate for this most JavaScript engines like V8 heavily optimize it. The result is that even bad code runs blazingly fast (sometimes even faster than good code because bad design patterns are well optimized). However that's no excuse to write bad code. Good code is not only fast but also readable, expressive, and easy to understand and maintain; and `augment` hits all these points. It's actually fast in Chrome 26. Slow in 27. – Aadit M Shah May 20 '13 at 08:10
  • Cool thanks, my reasons for changing to OO JS are all what you've just mentioned. I can't keep up with Chrome releases and can't wait to get shot of IE releases ;-). One final comment about the above code. If I use constructor then surely init is a duplication of the same thing? – Stokedout May 20 '13 at 08:39
  • @Stokedout - Yes, indeed. Your `init` method is redundant. That code should be moved into the `constructor` method. It's important that the constructor function be named `constructor` and nothing else. This is because all `prototype` objects in JavaScript have a `constructor` property. Hence it makes sense to use `constructor` as the name of the constructor function as this simplifies code. I made changes to my answer to reflect this. You should read more about the `constructor` property and prototypal inhertance in JavaScript here: http://stackoverflow.com/a/8096017/783743 Hope that helps. =) – Aadit M Shah May 20 '13 at 09:51
  • Checked out the post on constructor, wow you really do know your JS ;-) Thanks for your help on this one – Stokedout May 20 '13 at 10:23