Filip Minx
Filip Minx

Reputation: 2488

Defining prototype properties, the right way

I have a question about how you define your prototype properties. So you don't have to write "MyClass.prototype." to add every property to prototype, people create new object and set it in place of the original.

Like this.

var MyClass = function() {}

MyClass.prototype = {
    sayHi : function() {
        alert('hi');
    }
}

But if you do this, it could cause problems when you try to access constructor from any instance.

var o1 = new MyClass();

alert( o1 instanceof MyClass ); // true
alert( o1.constructor === MyClass ); // false !!!

o1.constructor would normally point to MyClass, but since the original prototype was changed, it doesn't anymore.

I managed to solve this by MyClass.prototype.constructor = MyClass; and it works right again.

The question is, what other problems could changing the original prototype cause?

How do You define your prototype properties ?

Upvotes: 3

Views: 267

Answers (4)

Aadit M Shah
Aadit M Shah

Reputation: 74204

I usually create "classes" in JavaScript inside out using a simple defclass function:

function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

This function allows me to create classes as follows:

var MyClass = defclass({
    constructor: function () {},
    sayHi: function () {
        alert("hi");
    }
});

This method has the following advantages:

  1. All the prototype properties are encapsulated within a single object literal.
  2. The constructor function itself is just another prototype property.
  3. Instances will always have the correct constructor property.

For instance:

var o1 = new MyClass;
alert(o1 instanceof MyClass);      // true
alert(o1.constructor === MyClass); // true

You can also easily modify defclass to support inheritance:

function defclass(uber, body) {
    var base = uber.prototype;
    var prototype = Object.create(base);
    var constructor = body.call(prototype, base), prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

You can then use it as follows:

var Rectangle = defclass(Object, function () {
    this.constructor = function (width, height) {
        this.height = height;
        this.width = width;
    };

    this.area = function () {
        return this.width * this.height;
    };
});

Inheritance is also just as simple:

var Square = defclass(Rectangle, function (base) {
    this.constructor = function (side) {
        base.constructor.call(this, side, side);
    };
});

Everything works as expected:

var sq = new Square(5);

alert(sq.area());                 // 25
alert(sq instanceof Square);      // true
alert(sq instanceof Rectangle);   // true
alert(sq.constructor === Square); // true

That's all folks.

Upvotes: 2

David Hellsing
David Hellsing

Reputation: 108490

The constructor property is just a convenience reference, you can simply reassign it:

MyClass.prototype = {
    constructor: MyClass,
    sayHi : function() {
        alert('hi');
    }
}

There shouldn’t be any other "problems" associated with redefining the prototype object, unless of course you are extending another object with predefined prototype properties.

If you want to be brave with your syntax, try using with. I wouldn‘t recommend it, but it’s still an option if you just want to shorten the syntax:

var MyClass = function() {}

with (MyClass.prototype) {
    sayHi = function() {
        alert('hi');
    }
}

Upvotes: 1

Greg Burghardt
Greg Burghardt

Reputation: 18783

I usually opt for:

function MyClass() {}

MyClass.prototype = {
    constructor: MyClass,

    foo: function foo() {
        // do something
    }
};

I realize it overwrites the constructor property, but it is cleaner to maintain and in practice I haven't noticed any problems in the 10 years I've been writing JavaScript "classes" this way.

The native Object.create method can also be used.

A while ago I created a small JavaScript library to assist in writing classes: Inherit.js

It allows you to create classes like this:

var Point = Object.extend({
    includes: [
        Mixin1,
        Mixin2
    ],
    self: {
        // class or "static" methods and properties go here
    },
    prototype: {
        // instance methods and properties go here
    }
});

var Point3D = Point.extend({ ... });

It provides support for "mixins" as well, if they are declared like this:

var Mixin = {
    includes: [
        // Yup, mixins can include mixins too
    ],
    included: function(Klass) {
        // Klass is the constructor function that just included this mixin
    },
    self: {
        // class or "static" methods and properties go here
    },
    prototype: {
        // instance methods and properties go here
    }
};

Some people would object to duck-typing native classes, but I'm all for it.

Why I went this route:

  • The instanceof operater still works
  • I wanted to inherit both instance and class level methods
  • I wanted something that would work well with most existing JavaScript frameworks
  • Mixins are supported, which can be a handy feature or a crutch, depending on the situation.

Upvotes: 1

SLaks
SLaks

Reputation: 887315

You can use a merge/extend function (commonly found in libraries like jQuery or Lodash):

$.extend(MyClass.prototype, { prop: ..., ... });

Upvotes: 1

Related Questions