I promised some people I would write a little about my ideas on inheritance in js. This is a code snippet from my procstreams
lib. https://github.com/polotek/procstreams/blob/chain-state/procstreams.js#L132-145
// this is a process object
if((cmd === process || typeof cmd.spawn == 'function')) {
// this is already a procstream
if(procStream.is(cmd)) {
cmd.on('exit', callback);
return cmd;
} else {
// this is a process that needs to be enhanced
proc = procStream.enhance(cmd, stateFunc);
}
} else {
proc = procStream.create(cmd, args, opts, stateFunc);
proc._args = o;
}
There are a few things happening here. I've long been in the camp of people saying instanceof
is a broken construct in js. There's no such thing as a static class that doesn't change, so it's useless to think of objects as being an "instance" of something. The IS A concept from traditional OO just doesn't hold up. Plus it goes against the idiom that js is flexible, and that monkey-patching objects is not only acceptable but encouraged.
Since that thing you get may not be the exact type of thing you expected, what you really want to do is find out if it has the capabilities you expect. Or put another way, you want to know if it follows the public api you expect to use. This is the concept of capability checking or duck-typing that js people are becoming familiar with. It's the same way we decide what features a browser environment has. Just ask objects about their properties.
But we want to do this in a more generic sense for objects that come from other modules. Above I'm getting an object called cmd
and I need to create a procstream
object based of that. So I'm inspecting cmd
to see what it is. Because of the way my api works, cmd
may already be a procstream. I want a nice way to check that, but instanceof
won't cut it here for various reasons.
So I introduce this idea of is()
. It allows the constructor for a "class" of objects to tell me if this object belongs. It takes in an object and checks its capabilities. If it passes, I get true
, otherwise false
. Here's what it looks like.
procStream.is = function(proc) {
if(proc) {
return typeof proc.spawn == 'function' && proc.pipe == 'function';
}
return false;
}
A procstream
actually has the capabilities of a node child_process and a node stream. I do a simple check for the primary features of these 2 things. Notice that capability checking doesn't have to be super thorough. These aren't the only 2 functions I might call on this object. You just have to find a combination that is unique enough that you're almost certainly right. If an object has these 2 functions and it's not a procstream, I've almost certainly lost complete control over my environment :)
Constructor.is(obj)
is not a perfect api for this. The ordering and name don't intuitively imply what it's doing. But it's short and sweet and nothing else seemed better. I'm open to better ideas.
The other thing that makes this snippet interesting is how I'm creating the procstreams. A procstream "inherits" all the behavior of a node child_process. But I can't actually override the creation of child_processes (because node doesn't expose the construction/prototype)[]. So I had to take a different route. Instead I have to get the child_process and "enhance" it with my own functionality. The procstream is actually the same object as the child process, it just has my own methods. You can take a look at the protochains
module to see how enhance
and create
work. And then @izs's inherits
module for the constructor stuff.
I wanna give a hat tip to @creationix. I remember him experimenting with various ways of creating objects a while back.
I'm still not sure about the drawbacks of this approach. But it works well given my constraints on this project. I'm always open to feedback.
With great power comes great responsibility. Monkey patching something is always a last resort for me. It usually indicates problems in my design or the libs I'm using.
As for the rest of this: I'm cool with duck typing, and yes
instanceof
is useless for that. But saying that it's a broken construct in js is a bit overkill, it just isn't a good tool for all problems.