JS Objects: Inherited a Mess

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

year 2013: Haskell people are still writing monad tutorials, JavaScript people are still trying to explain inheritance.

— Vyacheslav Egorov (@mraleph) April 12, 2013

As sad a criticism on JS as that quote is, it's quite true. (I have no perspective on Haskell or Monads, so I'm only talking about the JS and inheritance part). Of all the confusing and, depending on your biases, "bad", parts of this JS language, the behaviors of this and the [[Prototype]] chain have remained some of the most elusive to explain and use accurately.

As a bit of background, I've been developing JS full time since 2007. The first major epiphany I had back then was the understanding of how closures work, and how they enable the classic module pattern. The first open-source project I wrote (early 2008) was flXHR, a cross-domain Ajax prollyfill using the standard Ajax (XHR) interface (via a hidden flash element) that relied heavily upon the module pattern.

It's quite possibly my "ah-ha!" moment around the module pattern that satisfied me enough that I never really felt a strong need to also apply the "inheritance" pattern to my JS design.

Nevertheless, like most JS developers, I've read lots of blogs and books over the years that have tried (and mostly failed) to explain the appeal and mystery that is "JavaScript inheritance" (aka, "prototypal inheritance").

But if it's so hard to understand, and even harder to actually do correctly, the point yet eludes me. And apparently I'm not alone in that frustration.

OO in JavaScript

In traditional Object-oriented languages, the syntax of classes matches the semantics. You can express the object-oriented concepts of classes, inheritance, and polymorphism directly and explicitly using the language's syntax. There's no need to use some helper library to fake your way into OO-like behavior through work-arounds of other language facilities.

JavaScript on the other hand has a set of syntax that looks somewhat OO, but which behaves in frustratingly different ways (which we will cover throughout this article series). As a result, the common way that you implement OO patterns in JS is through any of a variety of user-land helper libraries which let you express the desired semantic relationships between your "objects". The reason most JS developers use them is because the underlying JS syntax makes those semantic expressions awkward. It's nice to just let a library handle paving over the confusing syntax hiccups.

Libraries like jQuery are useful because they hide the ugly details of dealing with cross-browser differences in JS engines. But these OO-helper libraries are different: they're going to great lengths to hide the true nature of JavaScript's OO mechanisms, instead masking them in a set of patterns that are more familiar to other languages.

At this point of understanding, we should really ask ourselves: is the difficulty of expressing classes and inheritance in pure JavaScript a failure of the language (one which can temporarily be solved with user librariesand ultimately solved by additions to the language like class { .. } syntax), as many devs feel, or is it something deeper? Is it indicative of a more fundamental disparity, that we're trying to do something in JS that it'sjust not meant to do?

Not everyone drank the JS classes kool-aid, so the rest of this article series will favor a different perspective.

Blueprint

One of the most common metaphors used in traditional class/inheritance OO is that the class represents a "blueprint" for a house to be built, but once you instantiate that class, you are basically copying all the characteristics from the blueprint into the actual built house. This metaphor partially matches, to an extent, what actually happens at a language level when the code is compiled, in that it sort-of flattens the definition of a class (sans "virtual" methods) inheritance hierarchy into the instance.

Of course, a main pillar of inheritance-oriented coding is overriding and polymorphism, which allows an object toautomatically access the most descendant definition for a method, but also to use super-style relative references to access ancestor (aka "virtual") versions of the same-named method. In those cases, the compiler maintains lookup tables for the virtual methods, but it flattens out the non-virtual parts of the class/inheritance definition. The compiler can determine a lot about what needs to be preserved and not and highly optimize the definition structure it creates in the compiled code.

For our purposes, we can think of traditional class-inheritance as basically a flattening "copy" of behavior down the chain to the instance. Here's a diagram to illustrate the inheritance relationship between a parent/base classFoo, and child class Bar, and then instances of each, respectively named foo1foo2bar1, andbar2. Visually, the arrows (aka, "copying") point from left-to-right and top-to-bottom:

Inheritance Arrows

What's in a name?

Despite the borrowed implications of the common name "prototypal inheritance", JavaScript's mechanism works quite differently, which we'll see in just a moment.

Both definitionally ("...characteristics transmitted from parent to offspring") and behaviorally (as described above), "inheritance" is most closely associated with the idea of "copying" from parent to child.

When you then take "inheritance" and apply it to a mechanism which has some very different behavior, you are asking for the confusion which has plagued "JavaScript inheritance" documentationeducation, and usage for nearly 2 decades.

To try to wade through this mess, let's set aside the label "inheritance" and its implications for JS, and hopefully we can arrive at something that is both conceptually more accurate and functionally more useful.

A.B.D's: Always Be Delegating

JavaScript's OO-like property mechanism for objects is notated by [[Prototype]], which is the internal characteristic of any object called its prototype-chain -- a special link to another object. It's kind of like a scope mechanism, in that the [[Prototype]] linkage describes which alternate object should be referred to if you request a property or method on your object which doesn't exist.

In other words, you're indicating an object to delegate behavior to if that behavior isn't defined on the object in question.

The above class-oriented Foo and Bar example, expressed in JS, relates object Bar.prototype toFoo.prototype, and then the foo1foo2bar1 and bar2 objects to their respective [[Prototype]]s. The arrows (not copies but live-links) point in a right-to-left, bottom-to-top fashion in JS:

Delegation Arrows

"Behavior delegation" is a more accurate term to describe JavaScript's [[Prototype]]. This is not just a matter of word semantics, it's a fundamentally different type of functionality.

If you try to illustrate behavior delegation in terms of the "blueprint" metaphor, you quickly see how it totally breaks down. There's no way that my home, lacking a guest bedroom, could simply refer to another house, or to the original blueprints, to provide a bedroom for my mother-in-law when she comes to visit. Though the outcomes you can achieve have some respective similarities, the concepts of "inheritance" and "behavior delegation" are quite different.

Some devs insist that "delegation" is just the dynamic version of "inheritance", like two sides of the same coin, but I see them as orthagonal systems.

How to delegate?

We'll revisit this later in the article series, but Object.create(..) was added to ES5 to assist with creating an object and then optionally linking its [[Prototype]] to another object. The link that is created is a delegation link, as opposed to an inheritance-by-copy.

Note: Once an object has its [[Prototype]] chain set at its creation, it should for the most part be considered set in stone and not changeable. Technically, browsers which support the __proto__ property, a public representation of the internal link, allow you to change at any time where an object is linked to. However, this practice is littered with landmines and generally frowned upon -- it's almost certainly something you'd want toavoid in your code.

Spade a Spade

You've seen how the mechanisms in JavaScript are comparitively different from the mechanisms in other languages. But is it ok to just hand-waive over these differences so we can keep using the term "inheritance" for JS?

The fact is, it's just not an accurate usage of the term. By insisting that JavaScript has "inheritance", we're really saying that the meaning of the word "inheritance" doesn't matter, or is rather soft.

JS doesn't statically analyze what parts of an inheritance chain it can safely flatten out and copy, it maintains links to the entire delegation chain throughout runtime, as distinct objects, which means our code can take advantage of a variety of powerful "late binding" dynamic patterns.

If we keep trying to mimic inheritance in JavaScript (syntax hurdles be damned), we get distracted and miss out on all that power that was built into our language from the start.

I say: let's call it what it is, and stop trying to pile on JavaScript these other concepts that the "inheritance" label implies.

So What?

So far, I've tried to identify some misconceptions about JS's [[Prototype]] mechanism and how "inheritance" is not a helpful label.

You may still be skeptical why it actually matters what we call this OO-like mechanism in JS? In the next part of the article series, I'm going to address many of the trappings of traditional "class-based" programming which I think are distractions that lead us to missing out on the essence of how JS objects interoperate. In fact, we could even say that classes/inheritance are a premature optimization for JavaScript.

Clearing those distractions out of the way leads us to part 3, where we'll see a simpler and more robust pattern for our JS code, and more importantly, our code will actually match our semantics without us having to jump through hoops to hide the ugly mismatches.

Look forward to parts 2 and 3 later this week!

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

  • By
    Conquering Impostor Syndrome

    Two years ago I documented my struggles with Imposter Syndrome and the response was immense.  I received messages of support and commiseration from new web developers, veteran engineers, and even persons of all experience levels in other professions.  I've even caught myself reading the post...

  • By
    Interview with a Pornhub Web Developer

    Regardless of your stance on pornography, it would be impossible to deny the massive impact the adult website industry has had on pushing the web forward. From pushing the browser's video limits to pushing ads through WebSocket so ad blockers don't detect them, you have...

Incredible Demos

Discussion

    • Can you point to the source where you’re finding classes are slow? For many objects spawned from a single class they’re actually quite fast and use less memory, in fact faster than spawning a new object literal for each.

  1. I’m fascinated to read parts 2+3 but I won’t remember your blog’s url tomorrow morning. I could (and will) subscribe to your RSS feed but I might not spot the next parts in my long list of things to read.

    If you’re going to do a multipart post – is there anyway to also have some email notification or something? I never catch the end of these type of things.

    • Maurizio

      I’m with Andy!!!

    • The next two posts will be published Tuesday and Wednesday respectively.

    • tomelders

      On the one hand, you’re a genius for thinking of email notifications for multipart posts. On the other hand, you’re an evil genius for ushering in the “posts-split-in-two-to-garner-emails” era.

  2. I think that trying to explain object oriented programming in javascript through real-life examples or through comparison with other languages is crippling the language. I think at some point I’ve done it too.

    Any programmer that has spent enough time in the JS land has heard at some point the cookie cutter example. However, after studying the language, and using it, you come to the realization that the approach we have taken over the years to explain/learn object oriented programming in javascript is simply wrong.

    We have tried really hard to explain what JS objects are, how they are different from objects in other languages, how there are no classes in javascript, and other concepts, that we have not taken the time to actually look at the real life application of those JS objects.

    I feel bad for those who come to JS from languages like Java, where everything is a class, and there is a heavy usage of inheritance, and other features/patterns offered by classical languages. I feel bad because they bring their mentality to a language that has great potential, and they cage it in the classical world.

    In turn, I feel bad for the rest of us. Unfortunately, those people coming from Java are the ones the language was written for, which is the main reason JS looks a lot like Java. It was those people who first started to “teach” the rest of us how to use javascript, and how to “fix” its “broken” object model. But the model is not broken, and JS is not Java.

    Truth be told, the inheritance model in javascript is messy, but who in the world said you even need to inherit? The language allows object extensibility, which, in my opinion is far greater than inheritance. An object can be extended at any point to add, remove, and better its functionality, which is basically why there is inheritance in other languages. Moreover, not only can an object be extended, it can be copied, and then the copy can be extended. It can also be assigned to another object’s prototype (this would be inheritance). Another interesting thing that an object can do in JS, is that it can borrow functionality from other objects. These characteristics make JS objects very powerful without the need for classes.

    I think we need to stop trying to fix something that has never been broken. It is not the language that needs to change, but the way we think of it. It is time to realize that JS is not Java, and that objects in JS are different from those in other languages.

    True, prototypal inheritance is messy, but we should stop teaching that, and start teaching people how to code without the need for inheritance. I would like to finish by quoting myself:

    “Truth be told, the inheritance model in javascript is messy, but who in the world said you even need to inherit? “

    • By the way, I look forward to the next two parts. You seem to be going in what I consider to be the right way by writing things like:
      “If we keep trying to mimic inheritance in JavaScript (syntax hurdles be damned), we get distracted and miss out on all that power that was built into our language from the start.”

    • Clem

      You have some good points there.

      I myself come from backend OOP development, I’m used to languages such as Java and PHP. As I learn more about JS, I get more confused about it, which might somehow explain the exponential growth of JS tutorials the OP talks about…

      Carrying my paradigm approaching JS, I don’t get how to use this prototype paradigm language (vanilla) without violating almost each of OOP/SOLID principles. Not sure that is bad ultimately, since that there might be better than OOP paradigm for building quality enterprise software, but which one then? How does a prototyping language can help me in that regard? This has to be defined yet. But then the question is:

      Isn’t the goal of a language to be adapted to us, to our way of thinking, to our type of application, rather than for us for us to try to adapt to this language afterwards, to try to find a benefit to it over the rest of the languages?

      JS looks like an experimental technology while it’s been around for 20 years and massively used in production…

  3. Sebastian

    Good article.

    JS is a prototype language that doesn’t embrace what a prototype language is meant to be. For a good implementation of a prototype language look at IO.

    In JS we have construtor functions and in my opinion they are the source of all confusion, they were added (I guess) to make the language look more like Java. In my opinion they don’t belong in a prototype language. But they are there and totally confuse people because – as they were intended – they make the language look like Java and people expect it to behave like that.

    I think that when teaching JS we should move the focus away from constructor functions and more into Object.create which exposes what JS is doing in a clear way. Even using __proto__ is a really good teaching tool, even if you are not supposed to use it.

  4. Using inheritance in JavaScript is kind of like accidentally taking a transvestite home from the bar. When you finally discover the difference, you are already in trouble…

    The biggest problem with JavaScript is the name. The “Java” part sets false expectations. Groovy should be called JavaScript and JavaScript should be called something else. It deserves a much better name than ECMAScript though…maybe something cool and flashy like ProtoScript.

    • Xavier

      What you just said about Groovy, Java and JavaScript is mind-blowing.

      You are a genius!

  5. Mateus Caruccio

    “and c++ peolpe still tring to understand any g++ error message” – that’s for your code.

  6. “comparitively” => “comparatively”

    Very interesting perspective.

  7. BigBossSNK

    Confucius says…
    When C++ child inherits mother’s eyes, child’s eyes stay blue.
    When JavaScript child inherits mother’s eyes, child’s eyes turn green if mother wears contacts.

  8. Sandro Pasquali

    It is difficult to say in advance what characteristics are essential for a concept. How do we represent knowledge about the generalizations we make from experience with concrete situations?

    In order for classical OO to work well the designer must have deep experience with the problem domain. Predicting all possible classifications of all concepts within a domain is difficult. Optimum class hierarchies are difficult to find, because they are hard to reason about, because we do not reason this way.

    We think in terms of prototypes. We iterate our understanding of problem domains. We evolve our thinking. When the map disagrees with the terrain, we trust the terrain.

    Modern software development involves the intelligent expression of significant data for average people striving to contribute to and work within an increasingly complex, information rich world.

    It is hard to see how software dependent on an information design ethic demanding foreknowledge of ever more obscure futures emerging from nearly impenetrable tangles of data associations and interrelationships can claim its ontology sufficiently expressive for the adaptive agility now necessary, given what we know about its historical difficulties with much more predictable domains.

    Perfect design does not exist.

    JS does not demand concordance with previous concepts, and as such each generation of object can use contemporary knowledge of domain facts to modify, suspend, cull or otherwise mutate precedent generalizations , allowing substantial change as better generalizations are discovered.

    • Clem

      Hi Sandro. Thanks for sharing your analyze, it’s very constructive, but allow me to respectfully disagree with. You say:

      >> How do we represent knowledge about the generalizations we make from experience with concrete situations? … Optimum class hierarchies are difficult to find, because they are hard to reason about, because we do not reason this way.

      From my experience we do tend to *generalize* a lot, instinctively, we reason that way. You speak about a group of things in general, like a group of people, even though you may discover later that there are exceptions in that group not inheriting the general rule (breaking Liskov substitution). Then what you do instinctively? You revisit your representation, abstract it further and decompose it further into more specific subgroups (applying Interface segregation principle).

      So we got more knowledge about the world. Our implementation of that world changed to reflect better our representation of it. In other words, any model has to change sooner or later as we get more knowledge of the world.

      So we can agree that we have to enable those changes to be implemented as simply as possible. OOP/SOLID principles are here for that.

      You say:

      >> We think in terms of prototypes. We iterate our understanding of problem domains. We evolve our thinking. When the map disagrees with the terrain, we trust the terrain.

      You’re not describing prototype thinking but generalization thinking, like I did above. So we agree on how we think, but not about how the language should conceptualize our way of thinking.

      A prototype is by definition itself specific, hence it is the opposite of a generalization, of an abstraction which is how we reason about the world being too complex for us.

      You say:

      >> JS does not demand concordance with previous concepts, and as such each generation of object can use contemporary knowledge of domain facts to modify, suspend, cull or otherwise mutate precedent generalizations , allowing substantial change as better generalizations are discovered.

      That’s exactly what is wrong with JS IMO. It doesn’t require concordance to general concepts and more importantly I don’t see how to make it do so, except than by alienating it with layers such as TS for example.

      If you have a concordance issue with your general concept is because your model is wrong and should be redefined STATICALLY to avoid further problems.

  9. Wow – thanks for the insightful article. I just finished part 1 and I’m stoked for parts 2 and 3. I’ve been writing JS for many moons and have never been able to overlap Java’s lovely inheritance with JS’s unique brand of ‘inheritance’. I think your article gets to the ‘why’ of that dissonance. Tonight: parts 2 and 3. Thanks again.

  10. This value indicates how frequently the content at a particular URL is likely to change.

  11. Simon Dell

    Thanks for this article, and even just the promise of the sequels. I’m in the throes of a second round of heavy JavaScript use in my career. During the first round I tried to apply what I’d learned about OO from university. I entered this second round having failed the first time, and subsequently forgotten much of classical programming through disuse. In reading of JavaScript tutorials, on patterns, the Good Parts, and writing plenty of code, I had started to feel all the talk of classes (and some of the practice) was missing the point of the language. I couldn’t put my finger on it. I worried that perhaps I’d not worked on large-enough projects to see the value; that maybe getting deep Into node.js might show me the OO light. But my gut said “no, use the language in the manor it was designed”. I’ve not yet spent the time needed to explore my gut’s suggestion fully, but I strongly suspect your article is going to provide some much needed insight. Can’t wait!

    • So glad to hear this article piqued your interest. Hopefully you’ve already found part 2 and part 3, which were published last week. Leave comments there and let me know how this series lines up with what you’re doing in JS!

      BTW, I’m so glad you’ve listened to your gut. That’s exactly the kind of thinking I’m advocating for.

  12. Just trying to wrap my head around the inheritance vs delegation concepts. Wondering why…

    Object.prototype

    …isn’t…

    Object.delegate

    Does that not make more sense? Or am I missing the point? (most likely I’m sure ;)

    • I can totally agree that `Object.delegate` would have been a lot more appropriate (less confusing, anyway). Unfortunately, there’s a whole bunch of mal-named things in JS. Just part of the mess we have to wade through. :)

    • Supersharp

      I’d prefer the name “prototype” for reasons exposed by Sandro Pasquali above.

      “We [humans] think in terms of prototypes”, not in terms of delegates.

      Anyway it’s just semantic: in JS the newly created object indeed inherits (the link to its prototype).

  13. Hi fellow programmers,

    I would like to tell you about classyscript at http://classyscript.com.

    It’s a javascript compiler allowing programmers to write a better javascript with compile time support.
    The language is a super set of javascript allowing classes and inheritance but compiling to standard javascript which will run in the browser.

    It also allows meta or macro style programming, please check it out.

    Thanks,
    Phil T

  14. Thanks for this wonderful post! It continues to be extremely helpful. I hope that you’ll proceed sharing your wisdom with us.

  15. james_lopez

    I am truly impressed with your blog content, your posts are truly high-quality and you are keeping it fine. I would like to issue my post on your blog (as guest post) with my website link. Mostly I create about educational and student related subjects. Please let me know if you are recognizing guest posts and I’m prepared to talk about my content, I promise you with unique, excellence and 100% plagiarism free content. I am looking forward to get your reply.

    Thank You

  16. Luke

    Interesting argument. The picture of “behaviour delegation” you draw here, however, reminds me a lot of how ruby implements inheritance, which leads me to believe that this is mostly just a matter of semantics and implementation, unless we want to claim that ruby also does not have classical inheritance.
    That said, I definitely agree that “behaviour delegation,” which I suspect is common to many dynamic languages not just ruby and js, provides a better mental model for being able to take advantage of late binding, runtime modification, and other features of dynamic languages.

  17. Thanks for this! I’m an older sw guy that has used everything from PL1, APL and LISP to Java, C and C++. (Mathcad was the first app I ever wrote in C back in 1985). I have long been disappointed that we try to make every language into OOP, whether Scheme or C++ or ObjectiveC. JS is interesting because it is more like LISP than Java, and should be used in ways that fit its unique character.

    I find statements like “GOTOs are harmful” much more harmful than GOTOs, because they promote orthodoxy and fashion. Turing machines can take infinite forms and all should be welcome if clear, well thought out and functioning.

  18. Clem

    Kyle, the contradiction you highlight between delegation and inheritance is well pictured as a pattern called “composition over inheritance”.

    That delegation mechanism of JS is simply a runtime/dynamic structure of a hierarchy. You can implement that pattern with Java also.

    You thought your hierarchy diagram in terms of child IS parent (static view).

    You thought your delegation diagram in terms of child HAS parent (dynamic/runtime view).

    There is nothing contradictory between “delegation” and inheritance. They are both the same thing, a hierarchy represented in different contexts: respectively dynamic and static contexts.

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