问题
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.
回答1:
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
andextends
way of doing things, but then I learned that evenclass
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.
回答2:
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.
回答3:
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';
});
来源:https://stackoverflow.com/questions/40517672/javascript-inheritance-and-hoisting