JS Objects: Distractions

By  on  

JS Objects: TL;DR

JavaScript has been plagued since the beginning with misunderstanding and awkwardness around its "prototypal inheritance" system, mostly due to the fact that "inheritance" isn't how JS works at all, and trying to do that only leads to gotchas and confusions that we have to pave over with user-land helper libs. Instead, embracing that JS has "behavior delegation" (merely delegation links between objects) fits naturally with how JS syntax works, which creates more sensible code without the need of helpers.

When you set aside distractions like mixins, polymorphism, composition, classes, constructors, and instances, and only focus on the objects that link to each other, you gain a powerful tool in behavior delegation that is easier to write, reason about, explain, and code-maintain. Simpler is better. JS is "objects-only" (OO). Leave the classes to those other languages!

Due Thanks

I'd like to thank the following amazing devs for their generous time in feedback/tech review of this article series: David Bruant, Hugh Wood, Mark Trostler, and Mark McDonnell. I am also honored that David Walsh wanted to publish these articles on his fantastic blog.

Complete Series

In part 1 of this article series, I went into great detail (aka, wordiness) about the differences between what the traditional definition of "inheritance" means and how JS's [[Prototype]] mechanism works. We saw that JS operates oppositely to "inheritance", being better labeled as "behavior delegation". If you haven't read it and you have any twinges of doubt or confusion about that statement, I'd encourage you to go read part 1 first.

Inheritance implies, to an extent, copying of behavioral definition down the chain, whereas behavior delegation implies delegating behavior up the chain. These aren't just word semantics, but an important distinction that, when examined, can de-mystify a lot of confusing stuff around JS objects.

I'm by far not the first dev to realize this truth about JS. What differs here is in my reaction to that realization. The response usually is layering on other concepts to smoothe out the rough edges or unexpected consequences of how "prototypal inheritance" can surprise us, to try to make JS feel more like classical OO.

I think those attempts just distract us from the plain truth of how JS works.

I would rather identify the things which are merely distractions, and set them aside, and embrace only the true essence of how JS's [[Prototype]] works. Rather than trying to make JS more "inheritance friendly", I'd rather rip out everything that confuses me (and others) into thinking JS has "inheritance" at all.

Types

It's often cited that in JavaScript, if you declare a function and add things to that function's prototype, then that alone makes a definition of a custom "type", which can be instantiated. If we were in a traditional OO language, that sort of thinking might be more appropriate, but here in JS land, it's just one of many distractions.

You're not really creating a new type in any real sense of that word. It's not a new type that will be revealed by thetypeof operator, and it's not going to affect the internal [[Class]] characteristic of a value (what would be reported by default via Object#toString()). It is true that you can do some self-reflection to check if an object is an "instance of" some function's construction (via the instanceof operator). But importantly,foo1 instanceof Foo is just following the internal [[Prototype]] chain of your object foo1 to see if at any level of that chain it happens to find the .prototype object attached to the Foo function.

In other words, the reflection you're doing is not about checking if the value is a specified type at all, nor is it about the function constructor. It's only about asking if one object is in another object's [[Prototype]] chain. The name and semantics of the instanceof operator (referring to "instances" and "constructor functions") are layering on extra and unnecessary meaning, which only confuses you into thinking there's anything more than simple [[Prototype]] chain checking going on.

Some developers frown on the usage of instanceof, and so an alternate form of determining the "type" of some object is called duck typing, which is basically inferring a value's "type" by inspecting the object for one or more charateristic features, like a specific method or property.

Either way, these aren't really "types", they're just approximations of types, which is one part of what makes JS's object mechanism more complicated than other languages.

Mixins

Another distraction is trying to mimic the automatic "copying" of inheritance by using the "mixin" pattern, which essentially manually iterates through all the methods/properties on an object and makes a "copy" (techically just a reference for functions and objects) onto the target object.

I'm not saying that mixins are bad -- they're a very useful pattern. However, mixins have nothing to do with the[[Prototype]] chain or inheritance or delegation or any of that -- they rely entirely on implicit assignment ofthis by having an "owning object" at the call-time of a function/method. They are, in fact, completely circumventing the [[Prototype]] chain.

Take any two independent objects, call them A and B (they don't have to be linked via [[Prototype]] at all), and you can still mixin A's stuff into B. If that style of code works for your situation, use it! But just note that it has nothing to do with [[Prototype]] or inheritance. Trying to think of them as related is just a distraction.

Another related distraction is when the inevitable desire to create "multiple inheritance" comes up, because JavaScript only allows an object to be [[Prototype]] linked to one other object at a time. When we read about the lack of multiple inheritance in JavaScript, several problems come up, and various "solutions" are often proposed, but we never actually solve them, we just do more fancy hand-waiving to distract us from the difficulties that JS poses at the syntax/semantic level.

For example, you basically end up doing some form of "mixin" to get multiple different sets of properties/methods added into your object, but these techniques don't, without elaborate and inefficient work-arounds, gracefully handle collision if two of your "ancestor" objects have the same property or method name. Only one version of the property/method is going to end up on your object, and that's usually going to be the last one you mixed-in. There's not really a clean way to have your object reference the different versions simultaneously.

Some people choose another distraction to resolve these problems, by using the "composition" pattern. Basically, instead of wiring your object C to both A and B, you just maintain a separate instance of each of A andB inside your C object's properties/members. Again, this is not a bad pattern, it has plenty of goodness to it.

Parasitic Inheritance is another example of a pattern that works around this "problem" that [[Prototype]] doesn't work like classes by simply avoiding [[Prototype]] altogether. It's a fine pattern, but I think it's a confusing distraction because it makes you feel like you're doing OO when you're not.

Whatever technique you use here, you basically end up ignoring the [[Prototype]] chain, and doing things manually, which means you've moved away from JavaScript's "prototypal inheritance" mechanism altogether.

Polymorphism

One particular distraction that ends up creating some of the most awkward code patterns we deal with in JS is polymorphism, which is the practice of having the same method or property name at different levels of your "inheritance chain", and then using super-style relative references to access ancestor versions of the same.

The problem is mechanical: JavaScript provides a this property, but importantly it is always rooted at the bottom of the [[Prototype]] chain, not whatever level of the chain the current function was found at. While it's true that this.foobar() might end up resolving (finding) foobar() at an ancestor level of the chain, inside that call, his this will still be the original rooted this object.

Put more simply, this is not relative, but absolute to the beginning of the call stack. If JS had a super or acurrentThis (as I proposed recently), then those references would be relative to whatever the currently resolved link in the [[Prototype]] chain was, which would allow you to make a relative reference to a link "above". But, JS does not currently have any such mechanism. And this being absolute rooted makes it an ineffective (or inefficient at best, thus impractical) solution to these relative references that polymorphism requires.

Most of the OO helper libraries try to provide a way for you to make super calls, but all of them end up having to do inefficient things under the covers to make that kind of relative call work.

class { .. }

Lastly, I think the long and hotly debated topic of class { .. } syntax that is coming to the language in ES6 represents more of the attempt to cover up what JS actually does with what people wished JS did. These sorts of distractions are not likely to make understanding JS's actual mechanisms better. Some speculate that it will make JS more approachable from traditional OO devotees, which may be true at first, but I suspect the ultimate result is that they'll quickly become frustrated about how it doesn't work as they'd expect.

What's important to understand is that the new class syntax we're getting is not introducing radically new behavior or a more classical version of inheritance. It's wrapping up how JS [[Prototype]] delegation currently works, in a syntax and semantics which come pre-loaded with lots of baggage understanding and expectation, which run quite contradictory to what you'll really get with JS classes. If you currently do not understand, or don't like, JS object "inheritance", the class {..} syntax is pretty unlikely to satisfy you.

Yes, the syntax takes away some of the boilerplate of explicitly adding items to a "constructor" function's.prototype object, and goodness knows we all will love not having to type the function keyword as many times. Hooray! If you already fully understand the awkward parts of JS "classes", and you can't wait forclass {..} to sugar up the syntax, I'm sure you're happy, but I also think you're probably in the minority. It's made far too many compromises to even make it into the language to fully please a broad range of totally opposite opinions.

The underlying [[Prototype]] system isn't changing, and almost none of the difficulties we just outlined are getting measurably any better. The only exception is the addition of the super keyword. That will be a welcome change I suppose.

Although, as a side note, the engine won't actually bind super dynamically (at call time) to the appropriate link in the [[Prototype]] chain, but will instead bind it statically (at definition time) based on the owning object of a function reference. This is going to possibly create some weird WTF's because the engine is going to have to create new function references on the fly as functions that use super are assigned around to different owning objects. It's possible (unconfirmed suspicion) that it may not work in all cases as you'd expect if super were instead bound dynamically.

Simplificationizing™

We've just examined a variety of ways that many JS devs try to layer on extra abstractions and concepts on top of JS's core object mechanism. I argue that this is a mistake that takes us further from the beauty of core JavaScript. Rather than adding complexity to smoothe out the rough edges, I think we need to strip things out to get to the good stuff.

In part 3, I will address exactly that, taking JS from the more complex world of classes and inheritance back to the simpler world of objects and delegation links.

Kyle Simpson

About Kyle Simpson

Kyle Simpson is a web-oriented software engineer, widely acclaimed for his "You Don't Know JS" book series and nearly 1M hours viewed of his online courses. Kyle's superpower is asking better questions, who deeply believes in maximally using the minimally-necessary tools for any task. As a "human-centric technologist", he's passionate about bringing humans and technology together, evolving engineering organizations towards solving the right problems, in simpler ways. Kyle will always fight for the people behind the pixels.

Recent Features

Incredible Demos

  • By
    Event Delegation with MooTools

    Events play a huge role in JavaScript. I can't name one website I've created in the past two years that hasn't used JavaScript event handling on some level. Ask yourself: how often do I inject elements into the DOM and not add an...

  • By
    JavaScript Copy to Clipboard

    "Copy to clipboard" functionality is something we all use dozens of times daily but the client side API around it has always been lacking; some older APIs and browser implementations required a scary "are you sure?"-style dialog before the content would be copied to clipboard -- not great for...

Discussion

  1. Haven’t quite got through the whole article yet, just wanted to give a massive thanks for putting the TL;DR at the beginning of the article. I don’t know why people keep on putting them at the bottom…

    Now, back to reading this behemoth of knowledge!

  2. Greg

    Awesome write up – a colleague of mine and I were just discussing this and how it confused us that people use OO methodologies when JS isn’t truly an OO language. I agree that they should just embrace it for what it is rather then try and make JS into something it’s not.

    • I think you misunderstood. Javascript IS a OO language, truly and factually. It’s an OO prototype-based, just not class-based.
      Is it also a functional language? Sure. Imperative? Yeah.

  3. Thanks! Great article, A must read, especially having read the first in this series http://davidwalsh.name/javascript-objects

  4. Mixins have nothing to do with prototypal inheritance except:
    1) They are an effective replacement for it (we use zero prototype chains on twitter.com).
    2) You can (and usually would) mix things into the prototype where they will be inherited by instances

    • While I agree that mixin is a perfectly sound pattern to use in and of itself, I think it’s a mistake (of the confusion kind) to try and “mix” mixins with prototypal inheritance, conceptually. I’ll bet 99 out of 100 JS devs don’t realize they are short-circuiting the [[Prototype]] chain when they do mixins, and so I think the patterns create more confusion when combined than they’re worth.

      So, I’m fine with the statement that you use mixins as a replacement for delegation.

      However, this article series is meant to uncover the hidden and confused power of behavior delegation itself, and so keeping that concept separate is important, and thus my point here.

      > mix things into the prototype where they will be inherited by instances

      Part of the confusion that I think abounds is when we start conflating the idea of “inheritance” between two classes (a base/parent class and a derived class) and then saying that an object that’s “instantiated” thus “inherits” from the class too. In traditional class-oriented coding, there’s definitely an important difference between those two concepts and using the same term for them can lead to more confusion.

      Of course, in JS, there *is* no difference, because there’s no such thing as classes, and it’s all just objects linked to other objects. IF you keep things clear in that way, and you stop using the term “inheritance” with all its baggage, then I’m ok with saying that the “mixin” pattern can be part of how you build up an object that you then delegate to. But once you introduce “inheritance” as a term into that, I think it all falls apart. :)

  5. Sebastian

    @angus “we use zero prototype chains” is a contradiction with your second statement. If you do a mix-in into the prototype of an object then you are using prototype chains.

    • I think he means zero calls up the prototype chain, if all the functions are copied in to an object then they will all be instance properties and won’t call up, though I suspect if it’s similar to using _.extend it’s still using a single prototype, just not going up the chain beyond that

  6. Well put.

    Some more hopefully helpful tips:

    “hand-waiving” => “hand-waving”

    “smoothe” => “smooth”

  7. It should be possible to create a currentThis shim with the proxy object and calling up the __proto__ chain. Then you’d also be able to put in logic for multiple “inheritance”.

  8. Hi,

    very nice article. I completely agree with you. The problem with JavaScript is that it’s almost always a second language for people. When programmers try to apply the patterns from other languages they often get frustrated because, well, JavaScript IS different.

    It is true that JavaScript has some really bad parts (like globals, hoisting, evil eval, …) but if you can look past these and use it as it’s meant to be used, it’s a really powerful tool and a beautiful language.

    It’s almost a given these days the the only road to code reuse is through inheritance, while there are actually a lot of ways for effective code reuse and maintainability.

    Good post! Looking forward to the third part.!

  9. Nice writeup man!
    I’m eager to read part 3.

  10. Rick S.

    This post is so on target that it is astonishing to me that more comments have not been made in the near 2 years since it was posted.

    It has been said that about 99% of JavaScript devs do not understand the full power of the language. Yikes, might be true(?).

  11. jikleshoog

    What a wonderful piece of writing this (only?) 3-part series is! If there was a playboy magazine for brains, you brain would be on the cover!

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!