0

In my current web project, I'm working with multiple JavaScript files, each containing type definitions that inherit from other types. So in any given file, I could have something like ...

function Type(){
    ParentType.call(this);
}
Type.prototype = Object.create( ParentType.prototype );
Type.prototype.constructor = Type;

... with the ParentType declared similarly in another file:

function ParentType(){
    this.data = "";    
}    

Since working with many JavaScript files becomes bothersome in the <head> tag, I wrote a bash script to concatenate the files into a single file (called master.js). That way, I can link a single file in my HTML.

For types that have only a single child, the prototype chain is built correctly, regardless of the order of concatenation of my files. In other words, this snippet ...

function ParentType(){
    this.data = "";
}

function Type(){
    ParentType.call(this);
}
Type.prototype = Object.create( ParentType.prototype );
Type.prototype.constructor = Type;

... acts identical to this snippet:

function Type(){
    ParentType.call(this);
}
Type.prototype = Object.create( ParentType.prototype );
Type.prototype.constructor = Type;

function ParentType(){
    this.data = "";
}

When I create an instance of Type in either scenario, ParentType.prototype.isPrototypeOf(typeobj) returns true (where typeobj is my instance of Type).

However, when I add another type to the end of the "prototype chain", it only works when the files are concatenated in order, i.e.:

function ParentType(){
    this.data = "";
}

function Type(){
    ParentType.call(this);
}
Type.prototype = Object.create( ParentType.prototype );
Type.prototype.constructor = Type;

function ChildType(){
    Type.call(this);
}
ChildType.prototype = Object.create( Type.prototype );
ChildType.prototype.constructor = ChildType;

... and otherwise, the chain "breaks". My guess as to why this is okay in a single-child scenario is because both type definitions get hoisted, and there is only one set of statements to worry about regarding the prototype chain. But for multi-link prototype chains, if the statements are out of order, the chain fails to connect properly.

So what I'm really asking is, is there a way to implement inheritance in JavaScript that "works" regardless of the order in which my files are concatenated? My first though was the class and extends way of doing things, but then I learned that even class definitions aren't hoisted!

Note: by "works", all I mean is that subtypes inherit functions/values from (all of) their parents, and isPrototypeOf returns true for any object when checked against any of its parents' prototypes.

bas
  • 57
  • 5

3 Answers3

0

The functions are hoisted, so they can be out of order, but the calls to chain the prototypes must be in order.

If they are out of order, your code will look like the following after hosting.

function ParentType(){
  this.data = "";
}

function Type(){
    ParentType.call(this);
}

function ChildType(){
    Type.call(this);
}
ChildType.prototype = Object.create( Type.prototype );
ChildType.prototype.constructor = ChildType;

Type.prototype = Object.create( ParentType.prototype );
Type.prototype.constructor = Type;

That is, you chain ChildType.prototype toType.prototype and then you overwrite Type.prototype to chain it to ParentType.prototype.

There is no way to make this work out of order, JavaScript inheritance depends on those lines of code being called in order.

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • I know this already. What I'm asking is if there is a way to implement inheritance where the order of code doesn't matter. – bas Nov 09 '16 at 23:38
  • 1
    If you knew it already, why does your question even mention hosting? – Ruan Mendes Nov 09 '16 at 23:41
0

One way to do it is to declare a static initializer for each type.

However, this is only possible at execution runtime (via Object.crate or via assignment) and therefore we are back at start. If your files are really concatenated in random order, then you should use more complex mechanisms with a sort of "factory" function that will create the types as they appear and put subtypes "on hold" until their parent is created.

Simplified example just for illustration purposes:

function factory(precondition, callback)
{
     if( !this.registry ) this.registy = [];
     if( !precondition || this.registry[precondition] ) 
         this.registry[callback()] = true;
     else setTimeout(function(){factory(precondition, callback);}, 100);
}

factory('Type', function()
{
     Window.ChildType = function(){}
     return 'ChildType';
});

factory(null, function()
{
     Window.Type= function(){}
     return 'Type';
});
Simon
  • 2,353
  • 1
  • 13
  • 28
0

For multi-link prototype chains, if the statements are out of order, the chain fails to connect properly.

Yes. You need to set Type.prototype before using it to create ChildType.prototype. This doesn't have to do anything with the hoisting of the function declarations.

Is there a way to implement inheritance in JavaScript that "works" regardless of the order in which my files are concatenated?

Well, you can might use Object.setPrototypeOf:

function ChildType(){
    Type.call(this);
}
Object.setPrototypeOf(ChildType.prototype, Type.prototype);

function Type(){
    ParentType.call(this);
}
Object.setPrototypeOf(Type.prototype, ParentType.prototype);

function ParentType(){
    this.data = "";
}

However, you really really want to avoid that method, and relying on hoisting like this is a very bad practice, so you really should fix your concatenation script to use the correct order. Or use a module system that figures dependencies out for you.

My first though was the class and extends way of doing things, but then I learned that even class definitions aren't hoisted!

That's a good thing. Just consider them to be purely sugar - you always need to set them up in the correct order, following the hierarchy.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • +1 for answering the actual question, this is pretty much what I was looking for ("exploring my other options"). It seems there's no good way to avoid refining my script unless I want to use an external source. – bas Nov 10 '16 at 02:37