Skip to content

Instantly share code, notes, and snippets.

@jeremyckahn
Created May 10, 2013 04:25
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • 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
@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