views:

45

answers:

2

Hello!

I'm trying to implement some kind of class hierarchy in JavaScript. I think I understood the prototype chain, but I still have to sort out the constructor-chaining. Following David Flanagan's Definitive Guide, I wrote

function DerivedClass()
{
    BaseClass.apply(this, arguments);  // chain constructors
    // do some initializations specific to DerivedClass...
}

var foo = new DerivedClass();

where BaseClass() is a native function of mine written in C++ (I'm using QtScript). My problem is that then, BaseClass() is called as a function, not as a constructor.

I could code BaseClass() to always behave as a constructor, however it is called. But I am afraid some day one of my users might forget new and just write

var bar = BaseClass();

In such a situation, I would like BaseClass() to do something more sensible than initializing the global object. For example:

if (!context->isCalledAsConstructor()) fail_gracefully();

But then the constructor chaining fails!

Is there a way I can chain the constructors and have BaseClass() actually be called as a constructor? Or should I just educate my users to never forget new? Right now I'm tempted to replace the test above by:

if (context->thisObject().strictlyEquals(engine->globalObject()))
    fail_gracefully();

but I wonder if there is a cleaner way to handle this.

Thanks!

A: 

You should educate your users to never forget new.

All "constructors" in JavaScript are just functions after all, so you can't protect against a constructor being called as a function.

It's incorrect JavaScript to try and create a new object without using new. Just because there isn't a "compile time warning" as there is in Java, doesn't make it any different.

EMMERICH
Well, technically I can protect them by either throwing an exception, or ignoring `this` and returning a brand new object (like `Array()` does). My problem is that the first option interferes with constructor chaining while the second does not play nice with prototype chaining.
Edgar Bonet
A: 

Answering myself...

I thought about my problem overnight... and I think I found a somewhat more satisfying solution: behave as a constructor if this is an instance of the calle. This test is somewhat stricter than checking whether it's not the global object, but it still allows constructor chaining, as long as the prototypes have been properly chained.

Here are the first lines of my native constructor (SerialPort is my base class, built around QSerialDevice):

/*
 * Should we behave as a constructor?
 *
 * We could use context->isCalledAsConstructor() to decide. However,
 * we may want to subclass SerialPort in JavaScript and chain the
 * constructors:
 *
 *      function DerivedClass()
 *      {
 *          SerialPort.apply(this, arguments);
 *          // do some more initializations...
 *      }
 *
 * This would fail if we decided on the basis of
 * context->isCalledAsConstructor(). The test below is somewhat less
 * strict. It allows constructor chaining provided the prototypes
 * have been properly chained.
 */
bool behave_as_constructor =
    context->thisObject().instanceOf(context->callee());

The funny thing about this is: unlike isCalledAsConstructor(), this test can also be implemented in a JavaScript constuctor!

Edgar Bonet