6
\$\begingroup\$

I have been using the John Resig JavaScript class implementation in my web apps, but the tests shows it is really slow. I really find it useful for the way of extending objects, and the benefits got from having a better code and less redundancy.

In some post was explained that it was slow because of the how the _super method is handled. Since super is Java style, and most of time I develop in PHP, I made my own version of Resig implementation using the parent:: style (used in PHP), with the aim to make this faster:

(function () {
  this.Class = function () {
  };
  Class.extend = function extend(prop) {
    var prototype = new this();
    prototype.parent = this.prototype;

    for (var name in prop) {
      prototype[name] = prop[name];
    }

    function Class() {
      this.construct.apply(this, arguments);
    }
    Class.prototype = prototype;
    Class.prototype.constructor = Class;
    Class.extend = extend;

    return Class;
  };
}) ();

Case of use:

var Person = Class.extend({
  construct: function (name) {
    this.name = name;

  },
  say: function () {
    console.log('I am person: '+this.name);
  },

});

var Student = Person.extend({
  construct: function (name, mark) {
    this.parent.construct.call(this, name);
    this.mark = 5;
  },
  say: function () {
    this.parent.say.call(this);

    console.log('And a student');
  },
  getMark: function(){
   console.log(this.mark);
  }
});

var me = new Student('Alban');
me.say();
me.getMark();
console.log(me instanceof Person);
console.log(me instanceof Student);

Any opinion about this? Is this fast? What about correctness? Some test shows that is the fastest for the moment.

My next improvement will be to remove the scope .call(this):

this.parent.say.call(this);

to

this.parent.say();

For some strange unexplained (but explained) reasons this test is not working any more, raising a too much recursion error (but it worked some days ago!)

I updated the test and fix the issue with this test. This is leading to strange results. On Chrome 39 it reports to be the fast class definition, on Firefox 35 it seems to be slow.

\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review! This looks like a pretty good first question, so I'm sure you'll soon get useful reviews. \$\endgroup\$ – Edward Jan 15 '15 at 12:22
  • \$\begingroup\$ Thank you. I posted it first on stackoverflow, but in this years the stack sites have been really grow and specialized \$\endgroup\$ – albanx Jan 15 '15 at 12:56
1
\$\begingroup\$

(1) Any opinion about this?

Emulating language features/idioms which are not supported by the language itself (e.g. classes in JavaScript) does not seem to be a very good idea to me. JavaScript is not an classical OOP-Language (yet), but there are Prototypes:

(function(){

    var Person = Object.create(null);
    Person.name = "";
    Person.say = function Person_say () {

        console.log("Person: " + this.name);

    };

    var Student = Object.create(Person);
    Student.say = function Student_say () {

        Person.say.call(this);
        console.log("Student: " + this.name);

    };


    var PsychicStudent = Object.create(Student);
    PsychicStudent.say = function PsychicStudent_say () {

        Student.say.call(this);
        console.log("PsychicStudent: " + this.name);

    };


    var ParanormalStudent = Object.create(PsychicStudent);
    ParanormalStudent.say = function ParanormalStudent_say () {

        PsychicStudent.say.call(this);
        console.log("ParanormalStudent: " + this.name);

    };

    var p = Object.create(Person);
    p.name = "A";
    p.say();

    var s = Object.create(Student);
    s.name = "B";
    s.say();

    var s1 = Object.create(PsychicStudent);
    s1.name = "C";
    s1.say();

    var s2 = Object.create(ParanormalStudent);
    s2.name = "D";
    s2.say();


}());

For insights i recommend:

(2) Is this fast?

Emulation is probably slower than using native methods - i suppose. However, it depends on the underlying engine. V8 is faster than Gecko.

(3) What about correctness?

Depends on how you understand correctness. JavaScript is powerful enough to emulate classical inheritance to some extend.

(4) FYI

If you want a more Java-like access to the parent-prototype with "super"-sugar, you can do something like this:

(function(){


    Object.extend = function Object_extend (parent) {

       var child = Object.create(parent);

       child.super = function (name,ctx) {

            if (typeof parent[name] === "function") {

                parent[name].call(ctx);

            }

            return parent;

        };

        return child

    };

    var Person = Object.create(null);
    Person.name = "";
    Person.say = function Person_say () {

        console.log("Person: " + this.name);

    };

    var Student = Object.extend(Person);
    Student.say = function Student_say () {

        Student.super("say", this)
        console.log("Student: " + this.name);

    };


    var PsychicStudent = Object.extend(Student);
    PsychicStudent.say = function PsychicStudent_say () {

        PsychicStudent.super("say", this);
        console.log("PsychicStudent: " + this.name);

    };


    var ParanormalStudent = Object.extend(PsychicStudent);
    ParanormalStudent.say = function ParanormalStudent_say () {

        ParanormalStudent.super("say", this);
        console.log("ParanormalStudent: " + this.name);

    };

    var p = Object.create(Person);
    p.name = "A";
    p.say();

    var s = Object.create(Student);
    s.name = "B";
    s.say();

    var s1 = Object.create(PsychicStudent);
    s1.name = "C";
    s1.say();

    var s2 = Object.create(ParanormalStudent);
    s2.name = "D";
    s2.say();


}());

However, bear in mind, that "super" is also a foreign concept to JavaScript. You cannot type foreign syntax and expect it to behave identically with comparable performance. Additionally, you may emulate "super" as shown above, but you will have to use with great care, meaning 'ParanormalStudent.super' and 'this.super' may point to the same function, however, the latter will result in exceeding the call stack. This seems to me like a bad practice, since as a programmer you will have a tool which may either work or produce chaos.

\$\endgroup\$
  • \$\begingroup\$ My attempt above is wrong. Even your is wrong. This will not work with more then three parents. You cannot have a super method (unless you use the slow john resign definition or new ES 2015). \$\endgroup\$ – albanx Jul 16 '15 at 14:57
  • \$\begingroup\$ Why is it wrong if no more than tree parents are supported? One parent is sufficient for the majority of use-cases, and most programming languages don't even allow more than one. I would recommend the first approach with linear prototypal inheritance from a single parent, which is supported out of the box - classes are not. \$\endgroup\$ – maxmeffert Jul 16 '15 at 17:52
  • \$\begingroup\$ No sorry I mean more than 2 extesion: Person->Student->Physic_Student, this case will not work \$\endgroup\$ – albanx Jul 17 '15 at 8:15
  • \$\begingroup\$ Yeah, I see your point now, but that's due to 'this' in JavaScript has completely different semantics opposed to 'this' in Java or PHP. I see now that calls of the form 'this.super' will never work, instead you will have to use '<name>.super', so there is no recursion on the context-object. However, that also means, you cannot completely sugar parent.say.call(this) to parent.say(), because you still have to pass the context up the prototype chain. I'll update my examples with a more complex prototype chain. \$\endgroup\$ – maxmeffert Jul 17 '15 at 15:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.