Skip to content

Instantly share code, notes, and snippets.

@jeremyckahn
Created May 10, 2013 04:25
Show Gist options
  • Save jeremyckahn/5552373 to your computer and use it in GitHub Desktop.
Save jeremyckahn/5552373 to your computer and use it in GitHub Desktop.
Proxy-based inheritance pattern in JavaScript.
function inherit (child, parent) {
function proxy () {};
proxy.prototype = parent.prototype;
child.prototype = new proxy();
};
function Parent () {}
function Child () {}
inherit(Child, Parent);
var child = new Child();
console.log(child instanceof Child); // true
console.log(child instanceof Parent); // true
@getify
Copy link

getify commented May 12, 2013

I think it matters what we call things, because I think when we mislabel, we set ourselves up for confusion. There's nearly 2 decades of precedent of people misunderstanding how [[Prototype]] works and then blaming the language for its "inheritance" being busted/broken. I think a lot of that misunderstanding would have been avoided if more people had insisted we look at things as "behavior delegation" and eschewed all the OO stuff.

But anyway, great discussion, thanks!

@madbook
Copy link

madbook commented May 12, 2013

@getify is correct in saying that, if you are going to do this, you should probably use Object.create inside the inherit function since it is standard and (I assume) more efficient.

function inherit (child, parent) {
    child.prototype = Object.create(parent.prototype);
};

however, i have to disagree with dropping new completely in favor of an 'object only' approach. besides new being significantly faster than Object.create in nearly every browser, the syntax for actually intantiating objects is much cleaner with the new operator. Consider your example.

var child = Object.create(Child);
child.init();

vs

var child = new Child();

Maybe its personal preference, but I'd much rather have the verbose part of my code in the object definitions and keep the main application logic clean.

Also consider readability. When using only objects, the only context that I have when reading your code to tell me that it is intended to be used with Object.create is the capitalization of the variable name, and whatever comment you may add.

// looks like any old object literal
var Foo = {
    foo1 : 'abc',
    foo2 : 123
};

If i'm just skimming through the code, I might miss that, or assume its just an important object, not that its intended to be a prototype. But with the normal constructor function approach, we have several clues: function declaration syntax, function name capitalization, use of this inside the constructor function. It's immediately obvious what the intended use is.

// obviously a constructor function
function Foo () {
    this.foo1 = 'abc';
    this.foo2 = 123;
}

@briandipalma
Copy link

Copying from Reddit comment.

I am amazed that these conversations are still occurring, shim Object.create and use this pattern for classes/inheritance :

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create#Classical_inheritance_with_Object.create

ES6 classes are sugar for the above. There is no further discussion needed.

@getify
Copy link

getify commented May 12, 2013

@Offler

This is an extremely short-sighted comment. You're not giving anything new to the discussion that we didn't know 15 years ago. If what you're suggesting was "enough", and there really was "no further discussion needed", then we wouldn't have the vast mountain of incorrect thinking, confusion, etc that has clouded JS "inheritance" for the whole of its lifetime.

The ES6 class keyword is actually going to make the situation _way worse_. I think it's one of the worst things to happen to the language in the whole of its history.

There's most assuredly more discussion needed, because almost all developers continue to get it wrong in JS, and then blame the language when they try to do something that it's not suited (or intended) to do.

@getify
Copy link

getify commented May 12, 2013

@madbook

besides new being significantly faster than Object.create in nearly every browser

First off, that performance test you link to is flawed and has lots of extra noise to it that makes it hard to see the real point.

Secondly, even if Object.create() is _currently_ slower, that doesn't mean it will stay that way. There's a long history of people making coding choices based not on correctness of thought, but on reasoning about current performance characteristics, then the browsers come along and pave some cowpath and now the original reasoning is totally wrong. There's still a lot of ignorant developers that use Array#join() instead of string concatenation, when it's clearly terribly less efficient. There's dozens of other examples about things that used to be true of performance-based reasoning that are no longer true, and yet people never revisit that once they know it once, so they end up with really badly performing code.

I don't think it's wise to choose one pattern or another simply (or even substantially) based on a performance test. Object.create() is built in to the engine, and will thus be much more optimizable over the long-run. The more people who use it, the more likely the engines will decide to optimize it.

the syntax for actually intantiating objects is much cleaner with the new operator

You've picked _the one place_ where using Object.create() is a little more verbose/ugly than new style coding. There's a mountain of counter-examples where constructor-based coding, in JS, is more verbose and more complex, than objects-only coding.

For example, you list that it's nice that new Fn() automatically executes your constructor, but you don't mention that JS fails to do what C++ does, which is that parent constructors aren't automatically called. So, if you want them called, you have to do awkward "implicit mixin" style coding (or parasitic inheritance) with .call(..) and you have to hard-wire those calls together, because you can't use relative this references to get at the parent constructors ( since they, super confusingly, aren't in the [[Prototype]] chain).

Moreover, when you throw polymorphism into the mix (that is, trying to have two+ levels of your chain with the same method name), you can't do relative polymorphic references to methods, so (just like with constructors) you have to hard-code the connections and use awkard .call(..) jazz, in every single method that needs to make such references up the chain.

_Sure, all of that mess_ is addressable, depending on your stomach for such awkward syntax, BUT importantly _you will end up completely circumventing the [[Prototype]] chain_ to do it (mixins, .call(..), etc). Once you circumvent the [[Prototype]] chain, the question becomes: why did you go to all the "trouble" to wire up this "inheritance" chain when you can't even use it (you have to circumvent it far more often than you get to rely on it).

I wrote a long 3-part article series, the first 2 parts of which are dedicated to pointing out _all these problems_ with thinking about objects in JS with constructors and "traditional OO". I won't repeat that whole set of arguments here, but if you are interested in the counter-argument in its completeness, I invite you to take a read.

If you want a succinct side-by-side comparison of these two coding styles, take a look at this gist. Note that there is more "complexity" in the bottom snippet when it comes to having to manually call init(), but then compare that short-coming to the several places in the top snippet with more "complexity", like referencing .prototype properties, having to add everything to the .prototype, using .call(this), etc.

This is especially true when you compare the reflection tests (the end of each snippet), which complicates significantly in the usage code the process of inspecting and reasoning about the "type" of object your reference points to.

My point is that the tradeoff of having to manually call init() buys you significantly cleaner code in a lot of other ways.

@Alxandr
Copy link

Alxandr commented May 12, 2013

@getify

Honestly, I'm a bit afraid to jump into a discussion of this magnitude of fear to be booed out (also, my English is far from perfect), but here goes.

You've made a lot of excellent points about the use of Object.create and how it might make the code simpler. With regards to the class keyword they are to introduce in ES6, I must say I have mixed feelings. Part of me feels like throwing more confusion into the mix will only increase the frustration, however I can also see how it would help a lot of people having little to no experience in javascript to express what they want to do in a familiar way.

Which brings me to my main point; you see, one of the problems with programming in general, is that there is no way at all you're going to learn everything. People have limited resources with regards to learning programming, and the fact that a person with almost no programming experience at all can browse through the jQuery-documentation (because, let's face it, a lot of javascript is still about basic DOM-manipulation) and figure out how to make his button fade out is one of the beauties of javascript. And one of the reason why I think javascript has become so popular is the fact that you can get up an running in no time flat. You don't need to know how prototypes works in order to fill the needs of the basic programming-tasks.

I work regularly with a person who does web-programming 40 hours a week, client and server (only the client is in javascript). Their entire web-system uses ajax for saving, and there are a lot of dynamic features. His experience is that of VBS, and he does not know how a javascript-object works at all. Tail-chaining of functions, lambdas, prototypes, callbacks, none of these does he understand how works, yet that does not hinder him in doing his job. And he does it well too. Sure, a lot of the code could be written better, and some DOM-lookups could have been saved, not to mention the fact that if you want to store a list of database-posts you could do it in a js-variable instead of writing it out to a html-table, but everything still works as it's supposed to do. And it can only do that because javascript is made in such a way that nomatter how little about the underlaying you actually understand, you are still able to express a fair share of programming-intent, and javascript will understand it.

And no matter what coding-style you have, javascript can sort of do that too. I mean seriously: You come from programming-languages using only simple variables (int/string/array), and global procedures? Sure, javascript can do that. You don't want to create anything public at all, everything should be hidden away in closures? Sure, no problem. You're used to classes and inheritance like in java? That too can be handled. Sure, several of these (if not all) have some problems, but it's enough to get you started. To get you learning.

And neither of these should be considered wrong. They are simply options.

Well, at least that's what I think.

@getify
Copy link

getify commented May 12, 2013

@Alxandr

First off, thanks for your thoughts. I think they are welcomed in the conversation.

You make a good point that one of JS's strengths is that you can "get the job done"™ without always understanding the underlying mechanisms to any great detail. It's certainly made JS a much more approachable language for the "masses" than many other languages.

Of course, the perspective that I'm trying to bring to light here in this thread, and in my blog posts, and in the books I'm about to write, is that there IS a way to learn and understand JS at a deeper level and that in doing so, you will increase your ability to write high quality, performant, and maintainable code.

It's a level of maturity of coding styles that I think we should encourage all JS developers to strive toward, but knowing that (unfortunately) many of them will not care nor have the impetus to do so. The more that do, though, the better the overall community will be, I believe.

With particular respect to me suggesting a simpler way of tapping into [[Prototype]] by looking at it as "behavior delegation" and not as "inheritance" (with all the baggage and confusion that brings in JS), I offer these two quotes:

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. --Antoine de Saint-Exupery

Any fool can make something complicated. It takes a genius to make it simple. --Woody Guthrie

For me, simpler is almost always better. Anyway, thanks for your input. :)

@getify
Copy link

getify commented May 12, 2013

@Alxandr

however I can also see how [ES6 classes] would help a lot of people having little to no experience in javascript to express what they want to do in a familiar way.

Sure, it's going to smoothe over some of the awkward .prototype syntax, but since it's _only syntax sugar_ and not going to change the underlying mechanism in any way, it isn't going to fix any of the pitfalls of how so called "prototype inheritance" is different from "traditional inheritance".

For instance, you're still going to be able to shoot yourself in the foot, accidentally and too easily, by changing something in the parent level of your chain, and _unlike all other traditional class-based languages_ you're going to confusingly affect your children. The metaphor is: "Sure, my son inherited many of my genetic traits, but when I break my leg, he shouldn't have his broken too!"

This, and several others, are long-time gotchas in the language around the objects system, and almost none of the writing and education over the last nearly 20 years has done much to address that confusion for new (and even mildly seasoned) JS devs. That tells me we're doing something wrong, and as the old adage goes, "only a fool keeps doing the same thing over and over expecting a different result".

So, now, ES6 is going to come along and make it even easier to create these relationships, and at the same time give them an even more "traditional" label (by calling it class { .. }). That is almost certainly going to make it even easier for new and mildly seasoned JS devs to stumble down the path with incomplete understanding, and when they run into those footguns, they're going to have even more ammunition to blame the language.

Taking a step back, and embracing what JS is _and what it's not_, gives us a chance to reset our thinking enough to possibly have a chance to avoid those footguns. At least, that's what I am trying to do. :)

@briandipalma
Copy link

@getify

Yes true that was a brusque reply it's just frustrating to read yet another reddit thread about inheritance in JS as if it's some amazing new discovery.

I think class is excellent sugar, you seem to be worried about people accidentally changing super classes and that affecting the child classes - I must say I've never come across such bad code in my life. Dynamically modifying class structure at runtime is such an obviously bad thing to do I think the issue is far less important then you may think.

If that's the only issue you can think of that could cause problems then I think most of us can sleep soundly at night.

@getify
Copy link

getify commented May 13, 2013

@Offler

Well, I've mentioned several problems (that just being one of them) here, and I wrote a 3-part blog post series about it recently, too, laying out my case: http://davidwalsh.name/javascript-objects

It's probably not prudent or useful for me to re-write all that in this thread, so if you are at all interested in the counter-argument against constructor-based coding, you might take some time to read them. But I assure it's much more than just one little argument.

I'm glad you've never seen code which confuses the fact that changes to a parent affect children, but I assure you, it's a long established footgun in JS that's tripped up scores and scores of developers. I can't even count how many blog posts and books I've read which get it wrong, too. Just because _you_ don't struggle with that issue by no means proves that it isn't an issue for the greater JS community.

@rhysbrettbowen
Copy link

@Offler it's not all about changing the actual classes at run time. You can write container objects that can take an instance and use that instance as a prototype, effectively creating a new "enhanced" object that you can pass along and use as if it was the old one but with extra functionality while still also retaining the old object.

I use this approach to create filtered collections in backbone where I can create a filtered collection that can be used the same as whatever is passed in (including the extra functionality that was put on top of that collection).

What you're really doing in this case is you want to delegate the behaviour up the chain rather than inherit it (though there can be issues with "this").

@getify
Copy link

getify commented May 13, 2013

Here's a couple of the gotchas where "prototypal inheritance" can be confusingly different (to a seasoned classical inheritance dev):

function Dad(){}
Dad.prototype.limbs = {
   leftLeg: true,
   rightLeg: true,
   leftArm: true,
   rightArm: true
};
Dad.prototype.break = function(limb) { this.limbs[limb] = false; };

function Son(){}
Son.prototype = Object.create(Dad.prototype);

var mydad = new Dad();
var me = new Son();

mydad.break("rightLeg");
me.limbs.rightLeg; // false! my dad broke his leg so my leg is broken?!?

In this example, the gotcha is that as a classicist you'd be expecting that the properties in the parent Dad class would be "copied" to the child Son class at inheritance time, so that mydad and me were not sharing the same limbs property. But in JS, a property is "shared" (via delegation) unless you manually make a copy, or just never assign data properties to anything but the instance (the this).

Of course, once you understand this gotcha, you can avoid it, but it's one of those weird things that trips up a lot of devs when they first come to JS's version of OO.

Another gotcha (of any object prototypes, regardless of classes, honestly), this time in reverse:

function Parent(){}
Parent.prototype.something = 42;

var p = new Parent();
p.hasOwnProperty("something"); // false

p.something--; // apparently the same as `p.something = p.something - 1`
p.hasOwnProperty("something"); // true!!

Most developers from other languages aren't terribly familiar with the concept of LHS vs RHS as it relates to variable references. The expression p.something = p.something - 1 has both going on, subtlely. The right-hand p.something looks up the current property, and finds it via delegation up on the Parent.prototype object (not on the p object).

But then the left-hand p.something is an assignment, and assignment follows a different process than property lookup. It assigns a new property on the owned object (the p) if one didn't already exist there, so instead of changing Parent.prototype.something, it creates a new p.something.

Furthermore, the usage here of the -- decrement operator is usually seen as being an "in-place" operator, since it doesn't have an explicit assignment in it. But in reality, under the covers, there is an assignment back to the property, which opts you into that confusing and not-terribly-obvious property shadowing.

You can closely analyze these behaviors and understand perfectly what's going on here. But _most_ JS devs don't fully understand those processes, and especially developers coming from other languages, this sort of thing can look really strange. It's definitely the source of much "blame" placed on the design of the language.

@getify
Copy link

getify commented May 13, 2013

BTW, earlier in this thread (/cc @madbook), the complaint was made that constructors not being called was a pain of the OLOO style of code. I agree. I've come up with a decent way to address that concern, IMO:

var Foo = {
    Foo: function(who) {
        this.me = who;
        return this;
    },
    identify: function() {
        return "I am " + this.me;
    }
};

var Bar = Object.create(Foo);

Bar.Bar = function(who) {
    // "constructors" (aka "initializers") are now in the `[[Prototype]]` chain,
    // so `this.Foo(..)` works easily w/o any problems of relative-polymorphism
    // or .call(this,..) awkwardness of the implicit "mixin" pattern
    this.Foo("Bar:" + who);
    return this;
};

Bar.speak = function() {
    alert("Hello, " + this.identify() + ".");
};

var b1 = Object.create(Bar).Bar("b1");
var b2 = Object.create(Bar).Bar("b2");

b1.speak(); // alerts: "Hello, I am Bar:b1."
b2.speak(); // alerts: "Hello, I am Bar:b2."

My convention here is that I don't call my constructor/initializer "init", but instead name it the same as the object it belongs to (sorta like real OO constructors, right?). By having these functions on the objects themselves, rather than having the objects belong to the functions, now the parent "constructors" are on the [[Prototype]] chain, and can easily be accessed without polymorphic problems using this style references, as shown. I also allow the "constructors" to be chain-called since they explicitly return this.

That lets us create the object, and initialize it, in one line: var b1 = Object.create(Bar).Bar("b1");. No, it's not as graceful as the attractiveness of automatically new-called constructors, but it's not that bad, and as I've asserted above, OLOO solves a lot of other ugliness of constructor style code patterns.

@Alxandr
Copy link

Alxandr commented May 15, 2013

@getify
I think your example on the broken legs are a terribly faulty one. If you create an instance of an object, said object is passed by reference, and there will only ever be one of the object no matter how many instances of the reference you hold. This is true in a lot of languages, only primitives are passed by value. So no matter where you're from, it should come as no surprise that me.limbs is exactly the same as mydad.limbs. You also seem to forget the fact that while you speak a lot of delegation, there's actually a lot of copying happening behind the scenes. When you access a property on the prototype it is actually copied into the instance. For instance, take a look at the following (rewritten) example:

function Dad(){}
Dad.prototype.brokenLeg = false;
Dad.prototype.break = function() { this.brokenLeg = true; };

function Son(){}
Son.prototype = Object.create(Dad.prototype);

var mydad = new Dad();
var me = new Son();

console.log('my leg is broken: ' + me.brokenLeg); // no legs broken ofcause
console.log('dads leg is broken: ' + mydad.brokenLeg); // no legs broken ofcause
mydad.break();
console.log('my leg is broken: ' + me.brokenLeg); // still no legs broken
console.log('dads leg is broken: ' + mydad.brokenLeg); // dad's leg is broken

If you remove the first check on me.brokenLeg though, you get true instead of false. The point here being that the prototype-chain (if you want to use it for sorta regular OOP) should maintain functions and other read-only values. If you want to have instance-spesific values, you assign them to this in the constructor.

Oh, and on your quote on simplicity. Simple does not necessarily mean not complex or in lack of options, sometimes it simply means easy to use. Sometimes bloated with options is the simpler alternative becuase people will recognice things (that may or may not be similar to what they think it is), and it's a place to start for them.

@getify
Copy link

getify commented May 16, 2013

@Alxandr

Sorry but it's your post that is "terribly faulty". Let me address several things:

If you create an instance of an object, said object is passed by reference, and there will only ever be one of the object no matter how many instances of the reference you hold. This is true in a lot of languages, only primitives are passed by value. So no matter where you're from, it should come as no surprise...

Either you have no experience (that you recall the details of) with true OO languages, or you completely missed the fact that my point was how the prototype system in JS is strange compared to those true OO languages, not strange compared to other prototype systems. I'm frankly not sure which is causing you to get this wrong, but you did miss the point, nonetheless.

In true OO languages, if I declare some protected member properties in a parent class, and then a child class inherits from that parent class, when you instantiate that child class, each child instance _will get its own copy_ of those member properties. AFAIK, in those languages, the only way to create "shared state" is to actually make static properties on a class. You can't accidentally do so in those languages.

Because of that fact, if you're from one of those languages, you're at least somewhat likely to first try to put some properties into a parent "class" (aka Dad) and thus assume that when your child "class" (aka Son) "inherits" from it, the child class (and indeed, all child instances) will get its own copy of those members.

Now, _I obviously know_ that this is faulty reasoning (ie, it won't work that way in JS), and that only member methods and shared member properties should go on the parent prototype, whereas non-shared member properties must always go on a this instance. It's a learnable fact, and once you do, JS's mechanism is tenable to use.

_But a new JS dev_ coming from a traditional class-oriented language is highly likely to miss (or be confused by) this difference, and many many many have. The proof is in google and stackoverflow history for the last decade+. A newbie JS dev writing my above snippet is doing so in good faith but is nonetheless going to trip over the problem quickly.

That's exactly what I meant in my previous post when I said:

Here's a couple of the gotchas where "prototypal inheritance" can be confusingly different (to a seasoned classical inheritance dev)

I suppose you missed that intended meaning?

When you access a property on the prototype it is actually copied into the instance.

Ummm, no, that's not how JS works. _Accessing_ a property via [[Prototype]] doesn't do any copying... if you try to set a property, then it will be set directly on the instance, which can look like copying, but it's not really copying, it's called "shadowing". In either case, it only happens when you try to set, not when you try to access.

If you remove the first check on me.brokenLeg though, you get true instead of false.

Ummm, no. Try it again. The console.log('my leg is broken: ' + me.brokenLeg); // no legs broken ofcause has no impact on the how console.log('my leg is broken: ' + me.brokenLeg); works.

In fact, your code snippet is quite subtlely confused. What does the copying of brokenLeg property is the break() method, because it says this.brokenLeg = .... When you call mydad.break(), mydad.brokenLeg now exists, where before it didn't exist and was instead delegating to mydad.__proto__.brokenLeg (aka Dad.prototype.brokenLeg). When mydad.brokenLeg property is created, he's now "shadowing" mydad.__proto__.brokenLeg.

So, after the call to break(), now you've switched to where you're confusingly comparing a shadowed owned property mydad.brokenLeg (which has been specifically set to true) with a delegated me.brokenLeg which of course is actually still delegating to me.__proto__.__proto__.brokenLeg (aka Dad.prototype.brokenLeg).

FWIW, that's the opposite of what you claimed about me.brokenLeg.

With all due respect, it appears your misunderstandings on this topic are somewhat of supporting proof of my overall point, that this stuff isn't terribly self-obvious and confuses lots (maybe most?) devs.

If you want to have instance-spesific values, you assign them to this in the constructor.

Yeah, duh.

But that wasn't the point of my previous code snippet. Again, to repeat myself, the point of my code was that a newbie JS dev would assume that, like in their previous real-OO language, those members on the parent class would end up automatically instance-specific.

It's not until later, when they run into all these gotchas, and learn the hard way, that they "get" it. So thanks again for pointing out what _we all_ (or most of us, anyway) _already knew_ but failing to address _what someone new expects_ when they first arrive at JS and hear about "classes" and think they work like classes work in traditional OO languages.

BTW, there are definitely uses for shared properties on the Dad.prototype. For example:

function Dad(first){ this.firstName = first; }
Dad.prototype.lastName = "Baker";
Dad.prototype.name = function() { return this.firstName + " " + this.lastName; };

function Son(first){ this.firstName = first; }
Son.prototype = Object.create(Dad.prototype);

var mydad = new Dad("James");
var me = new Son("Frankie");

console.log("My dad's name: " + mydad.name()); // "My dad's name: James Baker"
console.log("My name: " + me.name()); // "My name: Frankie Baker"

In this case, it's clear that neither Son.prototype nor me objects need a copy of Dad.prototype.name, because it's perfectly sensible to delegate up the [[Prototype]] chain for lastName. Side note: of course, once a theoretical Daughter instance like mysister gets married, she's then gonna need her own copy of lastName so it can change if she wants, but until then, it's fine to delegate as another of mydad's children.

Simple does not necessarily mean not complex or in lack of options, sometimes it simply means easy to use

This is an extremely common claim but is nevertheless misguided. Rather that re-count the explanation for the super important difference between simplicity/complexity and ease/difficulty, I'll just point you at the definitive argument: Rich Hickey's "Simple Made Easy" talk. No sarcasm. Seriously, go watch it. It was a ground-breaking, transformative talk when I heard it at Strange Loop a couple of years back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment