JS Objects: Inherited a Mess
davidwalsh.name
davidwalsh.name
It's not inheritance - it is delegation.
It's just like in several other object-oriented languages: Self, NewtonScript ( http://waltersmith.us/newton/OOPSLA95.pdf ), Object Lisp ( http://lispm.dyndns.org/docs/ObjectLisp-Manual.pdf ), MacFrames, ...
This is a classic example that it is difficult to unlearn a concept.
Q: how long does it take to learn prototype-based OO?
A: a day is sufficient. If you know class-based OO you'll need three days.
Isn't that the sad part? Its like Java / C++ is it for a lot of people and every other language has to fit that pattern.
Why assume prototypes are better or that people should adapt to whatever (potentially BS) way a language offers for organizing code, instead of the other way around (languages should adapt to the way people want to use them).
Whenever it comes to getting real work done, we see JavaScript developers resorting to some imitation of the proper class-based OO functionality made available by so many other languages.
At what point will people admit that JavaScript's attempt at prototype-based OO is a miserable failure? At least we're seeing some indication of this finally happening with the latest standardization efforts. It's unfortunate that this is coming so late, however, after so much JavaScript-based software has been written using a hodge-podge of the different ways to fake class-based OO.
1) to quote "how will I ever use and image-based language with source code control?"
> The JavaScript community has had well over 15 years to show that prototype-based OO is feasible. Yet we've seen nothing compelling produced in that time.
Nothing compelling produced in JavaScript?
A reader could be forgiven at this point for thinking that you're not only unfamiliar with the language, but also largely unfamiliar with what people have done on the web in JS.
> Whenever it comes to getting real work done, we see JavaScript developers resorting to some imitation of the proper class-based OO functionality made available by so many other languages.
Who's "we"?
Look, I don't know what your real criticisms of JavaScript's approach are, since you've chosen to spend your writing energy on making a few comments filled with vague pejoratives rather than any specifics.
So I'm going to go on a guess: from your perspective, "proper" OO programming is modeling out the problem domain of the program in a class/type hierarchy, with inheritance relationships as the primary mechanism for code reuse.
That can be a helpful way of organizing a program. It works well enough for some problem domains where you've got a lot of related objects that are mostly like some other object, and where the relationships between them are pretty clear. It works particularly well where there's a set of well-established conventions ("patterns") for definitions... or where you're actually writing software where someone's already formally modeled a problem domain in exactly this manner (though not all formally modeled domains are easily amenable to this approach, as anyone who's ever tried to write or use an ORM for non-trivial domain can tell you).
But it's not the only way to write software. Heck, it's not the only way to write object oriented software. And a class hierarchy is sometimes not the simplest or the most robust way of approaching a program.
The JS devs I know who are best at getting "real work" done recognize this. And they don't spend a lot of time stressing over modeling their programs as a class hierarchy. In some cases where that turns out to be the best model for a problem, it turns out JavaScript will support it (you used words like "imitate" and "fake" but the word "implement" would probably do just as well for a non-polemicist). But much of the time, composition and a bit of shallow prototype delegation or method grafting will do.
And that's for the space where objects are helpful... that's to say nothing of the domain where sometimes, functions and a spot of imperative code alone will do the job.
No, he means nothing compelling produced with prototypes, without resorting to some tricks to make it class-like. (Did you even notice the context he replied to?)
Now, here and there you'll find some prototype usage for what it is. But the overwhelming majority of frameworks, developers, and projects uses a class-like approach.
This has taken us to "enterprise" frameworks and layers of abstraction that offer very little actual value, and huge amounts of maintenance overhead. In VS/Eclipse, I can't tell you how many times I go through the inheritance chain only to be brick walled into an Interface, and then have to look for the specific/abstracted implementation of said interface.
99/100 times there is only one implementation, and no test coverage to justify said abstraction. So, why do it at all? Because of how strict and constrained said programming environment is.
JS doesn't have those levels of restriction, and doesn't need those levels of abstraction and inheritance. You can utilize other patterns and practices... there's more out there than Class based approaches.
For example MVC is really seamless in JS... you can have models that are dumb JS objects (they don't need to be inherited from, or to).. and modules that manipulate/use/store said objects. It doesn't need to be so complicated.
It isn't that some problems aren't complicated, and don't need some serious use of patterns/factories/IoC... It's simply that in many cases they are done because they "can be" or some overzealous architect thinks they "should" be. I've only ever seen a couple instances where most of these abstraction patterns actually make sense.. and I've been writing software for close to two decades now.
As for "but the overwhelming majority of frameworks... use class-like approach" .. arguably the single most used framework/library in use today is jQuery.. and that definitely does NOT use a class-like approach.
Here. A thriving web, mobile and native application ecosystem.
>Class-based OO approaches became popular in the mid-90's as a panacea of maintainability and code reuse. This has taken us to "enterprise" frameworks and layers of abstraction that offer very little actual value, and huge amounts of maintenance overhead.
Yes, only that has nothing to do with the classes. You could do the same bloated mess of enterprise layer upon layer, XML configuration, boilerplate etc, even with prototypes, if anybody cared about them.
And you can do perfectly clean designs, also with classes. Not even need to use Smalltalk, CLOS etc, you can even do them in Java if you take the necessary care. And lots of people do them in Python, Ruby, etc too.
>JS doesn't have those levels of restriction, and doesn't need those levels of abstraction and inheritance. You can utilize other patterns and practices... there's more out there than Class based approaches.
Nothing in using class based approaches necessitates those "levels of restriction, abstraction and inheritance".
People use Coffescript (which has "classes") and do perfectly lean work, without it looking like Apache Struts or something similar.
>As for "but the overwhelming majority of frameworks... use class-like approach" .. arguably the single most used framework/library in use today is jQuery.. and that definitely does NOT use a class-like approach.
That's because it is a DOM manipulation library, not a code structuring library itself. And people writing plugins with fuller functionality on top of jQuery (as jQuery/jQuery UI plugins) have been using OO styles on top of jQuery since for ever.
Well, it will be given one. With ES6 and it's class sugar. And people complain about it.
>It's like programming Smalltalk like it FORTRAN.
If that's what you want to do (and it works), then why not? Especially if that's what almost everybody using Javascript seems to want to do, and each utility library invents its own non compatible way of doing it.
Now, you picked a more flexible language first in your example, but how about "programming Java like it was Clojure" or "programming BASIC like it was Python"? Doesn't sound so negative anymore, does it?
Plus, isn't that "programming X like it was Y" what tons of people do in Javascript, when they use Clojurescript (like Clojure), Coffeescript (like Ruby), Amber.js (like Smalltalk) etc etc?
The semantics of the language remain JS underneath (prototypes, floats, etc -- it's not "true" Ruby, Smalltalk, Clojure), but you get a different facade on top. I don't see many complaining for that.
Yep, sounds just as bad. You are programming for something other than the language's strengths. You are also doing something that makes your code different from its conventions.
Plus, you aren't learning a new way of expressing the code or why the language designer did what they did, so you won't see the solutions a new language affords.
Cross compilers generated code is not meant to interop with hand written code. That is a different game.
I hate abstraction for the sake of.. and really, a lot of JS "Class" libraries are mostly that.
The current seems to be what the developers want to do. And JS doesn't really let them.
Why define the current as the arbitrary capability of a random language?
I don't think it's a problem with developers not understanding prototype-based OO; they do. It's just that they run into situations time and time again where class-based OO is a better option. Yet since JavaScript doesn't (yet) offer proper class-based OO functionality, developers needing or desiring such functionality are forced to implement it themselves.
As anyone who has worked on any sizable, multi-developer JavaScript code base will surely know, there are many different approaches to simulating proper class-based OO in JavaScript. The maintenance headaches this causes are very real.
The direction of the JavaScript current is just flat out wrong. It's not that developers should stop swimming against it; they're going in the right direction. The current itself should be reversed so that it aides the developers in the direction they want to go, rather than continually ramming against them.
Problems tend to occur when developers don't agree on code conventions. This has nothing to do with JavaScript or OOP.
http://dl.acm.org/citation.cfm?id=62144
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.55....
Can you give an example of such situation? My experience with prototype-based inheritance is quite different - I find that it can do everything that class-based inheritance can do, and more.
My life is much happier this way, it's effective and it works... Yes, it's much closer to a C-like way of dealing with libraries, but without all the messy issues like pointer logic or memory management.
People need to stop trying to shove JS's triangle into a OO's round hole.
I for one quite like being able to mix in other objects but exclude some properties/methods while still understanding what's actually happening under the hood without needing a Haskell-level type system.
Because one single feature (the above offering native classes) trumps stuff like browser support, libraries, or awkward development environment etc, right?
Or he could continue to work with JS, with a class based system, either ad-hoc (as 99% of JS developers use), or built in ES6.
ES6 sounds great since it would offer the best of both worlds, but it's going to take quite a while before you'll be able to do your everyday development in ES6.
Yes, but then you have to swim against other streams, like the languages not being mainstream, needing a translation layer ton run in the browser, etc etc.
If JS's facilities are viewed as inheritance or delegation, the language ends up being used in limited ways to express solutions. There is nothing sacrosanct about classes. I agree with the original authors of Self that one tends to freeze categories early on in a class based language, whereas in a prototypal system, the categories are fluid, with structure that can evolve over time as knowledge about the problem domain improves.
If I want to understand a programming language feature, I read the language spec, a manual, or, if all fails, the source code of the implementation.
While that's indeed an invalid expectation, the term "delegate" is used technically in Objective-C, for example, in a way that cannot be related to "prototypal inheritance". The "delegates" don't use the resources of the delegating object. Quite often, they provide resources or data to the delegating object.
JS's prototype system doesn't fit this model of "delegate" either 'cos the "delegate" does appear as though it is wearing the delegating object's shirt. The delegate itself (if you still want to call it one) has no identity apart from the delegating object in JS, whereas that isn't the case in ObjC.
> If I want to understand a programming language feature
This is not so much about understanding a language feature. It is about a model of system design being force fitted onto the language constructs. The language feature itself is dead simple and doesn't need to be viewed through the filter of OO to "understand" it.
If I would want to learn about prototype OO, I would check out the founding papers. Stuff like:
http://selflanguage.org/_static/published/parents-shared-par...
Parents are Shared Parts of Objects: Inheritance and Encapsulation in SELF
http://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/De... Using Prototypical Objects to Implement Shared Behavior in Object Oriented Systems
'Delegation' in Objective-C is a different mechanism.Objective-C taking a specific meaning for that term is nowhere near as ubiquitous as the point I made that "inheritance" has been so well defined across so many class-oriented languages (for decades!) that borrowing the term for [[Prototype]] is a confusing mistake.
For example, if I'm given a task by my boss, and he says "just make sure it gets done", I'll have a set of materials, say the specifications for a task, or an email thread discussing it, or something like that. When I decide to "delegate" it to another person on the team, I'm not going to expect that person to start from scratch... I'm going to hand them the "context" for them to tackle the task in. I'm going to say: "here's what you need to get started, go do it."
Metaphors are never perfect, by definition. But I take that perspective on your metaphor, and on JS's behavior delegation, and I think it fits decently.
Now, unfortunately, the native Javascript way of saying "please set the prototype of this object to X" is not very intuitive. So again we reused an existing word -- "class" -- and defined it to be "a thing that creates an object and sets its prototype to be what I want".
However, actually creating these class things is painful and verbose in vanilla Javascript, so everyone uses a library to do it for them. I use classdef [1] because I wrote it and it works just the way I want it to, but seriously there are a million options out there. Go find one and use it.
In JavaScript, the same concept exists, and it's sometimes called "prototypal inheritance" (since there are no classes).
Using inheritance to share code is usually better achieved by factoring that code out to a separate object and having the objects in a inheritance relationship use it by composition.
If I say that my object is a Customer then I know what can be done with it. If I have a method that says it takes a Customer object then the calling point knows what to pass in, and my method knows it can rely on that object satisfying certain conditions.
Basically it removes (some of) the need to document separately, and it enforces that the documentation is adhered to.
The trouble is that a lot less energy has gone into designing for non-classical prototypal inheritance, and what techniques exist are unknown to most programmers.
A few years ago, when I started learning Objective-C I used a book written by Aaron Hillegass. I liked the way he explained delegation vs. subclassing:
> Once upon a time (before Baywatch) there was a man with no name. Knight Industries decided that if this man were given guns and wheels and booster rockets, he would be the perfect crime-fighting tool. First they thought, "Let's subclass him and override everything we need to add the guns and wheels and booster rockets." The problem was to subclass Michael Knight, you would need to know an awful lot about his guts so that you could wire them to guns and booster rockets. So instead they created a helper object (delegate), the Knight Industries 2000 Super Car, or "Kitt."
> Notice how this is different from the RoboCop approach. RoboCop was a man subclassed and extended. The whole RoboCop project involved dozens of surgeons to extend the man's brain into a fighting machine. This is the approach taken by many object-oriented frameworks.
Source: Cocoa Programming for Mac OS X by Aaron Hillegass.
TypeScript has (I believe) a mostly compatible implementation of these classes if you want to try them out in today's runtimes.
Why? If the people want a language changed, most abuse the current prototype system to be more classic class like (including top notch programmers), and there is a demand for it, why not add a proper class syntax, even as a sugar for the common workaround?
It's like saying people must adapt to the idiomatic ways of a language without offering any proof that those are better (and no, random qualities like "more flexible" is not necessarily better in actual use).
I don't think we should worship "idiomatic" ways of each language. For example, tons of Beans, XML, and boilerplate was the idiomatic Java way, but then projects came along and showed it could be done better (e.g Play). Using every feature under the sun and abusing templates is kind of an idiomatic C++ way, but people are arguing successfully for merely using C++ only as a C with classes and some minimal features.
Evolving JS into language with minimal dynamic classes such as Python or Ruby should be easy as all interfaces are already "classical", you just need to add some syntactic sugar. This approach was already proved to work by CoffeeScript and TypeScript. I can't imagine how JS could be evolved into clean prototypal language such Io without breaking backwards compatibility.
There is no hope for JavaScript to become elegant prototypal language because of the design mistakes that were done in the past. All that TC39 / ECMAScript.next committee can do is put even more lipstick on a pig.
http://this-plt-life.tumblr.com/post/36425234595/when-somebo...
http://this-plt-life.tumblr.com/post/41438931672/when-somebo...
In parts 2 and 3, I'll make the case for why I think all those things don't belong in JS and should be removed or avoided.
Constructors and "new" were there from the beginning. Nobody have even noticed the prototypal nature of JS until people like Crockford started evangelising it 10 years later, which in turn resulted in the addition of Object.create() in ES5.
AIUI, functions were not originally first-class citizens. My understanding is (and I may very well be incorrect), in these very early days, functions not being objects precluded an awful lot of what we currently "know" to be true about how JS emulates "classes" with function.prototype. I tried to find good citations for this fact, but I'm having trouble finding good "early JavaScript" history.
I do know that at least as early as ES1, in mid-1997, we had constructors with prototypes and "new". [1]
In any case, besides `Object.create()` which you've noted, here are some other milestones in the evolution of "OO in JS" I could find:
`instanceof` was added in JavaScript 1.4 [2], which was sometime after Oct 1998 [3], probably officially in ES3.
`Object#isPrototypeOf()` was added in ES3 [4], which was around late 1999.
`Object.getPrototypeOf()` was added in ES5 [5], which was around 2009.
[1] http://www.ecma-international.org/publications/files/ECMA-ST...
[2] https://developer.mozilla.org/en-US/docs/JavaScript/Referenc...
[3] http://en.wikipedia.org/wiki/Javascript#Version_history
[4] https://developer.mozilla.org/en-US/docs/JavaScript/Referenc...
[5] https://developer.mozilla.org/en-US/docs/JavaScript/Referenc...
I hope you know what I mean. This concept of super-calls does not transfer well to prototypal inheritance for me. Are there other patterns that should be used instead?
PrototypeClass.method.call(this)in the end i also think super-calls are not a very good concept as you normally don't know what the super calls are doing internally, but somehow it seems like the way to go as almost every mainstream language/framework is supporting/doing it... but i guess that's the general bitter taste i get from classical inheritance...
> 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.
OTOH, I can delegate my mother-in-law to go stay in a hotel room. Or, I can delegate my mother-in-law to stay on a neighbor's couch, but I can send along sheets and pillows for her to use.
In either case, I'm not mutating my own home (inheritance) to handle her visit, I'm relying on someone else to handle it. But it's still my mother-in-law who I send elsewhere, and in fact, I can make sure she takes some "context" (aka, pillows) with her when she does.
Again, metaphors are never perfect, but I just thought I'd complete the perspective to contrast inheritance vs. behavior delegation.
There are however many other ways to do OO and JavaScript just happens to use another way. The language is not alone, Self, BETA, C@, Dylan, CLOS also have different ways from mainstream OO.
1. http://madebydna.com/all/code/2011/06/24/eigenclasses-demyst...
1. http://python-history.blogspot.com/2010/06/method-resolution...
I learned about non-OO JS code in the YUI framework. I didn't like what I saw as it precluded a lot of "private and hidden" code.
I've deposited a lot of goodwill on the Dart language that compiles to JS. My reward is that Dart has library and class support that "just works! (TM)" Then again, I abhor the using of types that Dart allows as I think people waste a lot of time giving types to APIs and obscure the intention of the code, ultimately making the platform less popular.
What do you mean? Adding types to the surface area (arguments and return types) removes a lot of friction.
I'm using jQuery for a couple of years. I still have to look at its docs almost every day I use it. Today, I had to check if the "selector" argument of "children()" is optional (spoiler: it is).
Also, having the type annotations there makes the intention so much clearer. The name and the type of the argument is usually all you need to figure out what it does.
I really like that about Dart.
The API designers end up creating a multitude of APIs to deal with different type parameters and so on, to get their "generics" right and so on.
It makes the APIs look like Java APIs more than they jQuery ones.
Part of the problem is that the designers really want to get the types of classes and so on right on their parameters and return types. The types go beyond the primitives.
I've been able to watch it unfold and it's rather painful. The result looks less like a scripting language API and more like a C# collection of libraries, for better or for worse.
I don't see any problem there. The code is very compact. There are lambda expressions, method cascades, and user defined operators.
Also, structure is baked in. You don't need any of that AMD boilerplate code. Annotations are also baked in. You don't need to write those lengthy doc comments.
Additionally, you can auto-complete everything.
> It makes the APIs look like Java APIs more than they jQuery ones.
With jQuery, you often have those "options" objects which force you to check the documentation even if your editor is somewhat jQuery aware.
With Dart, you can just use named arguments. It's also clearer what the default values are. Of course things are also a lot easier if the machine can tell what you're trying to do.
> more like a C# collection of libraries
Well, that's the whole point. If you put type annotations at the API boundaries, those annotations can be used for documentation (fully cross-referenced), call-tips, auto-completion, type inference, and basic sanity checks.
This gets rid of lots of friction. Even if your own code doesn't include any types, the editor will be able to tell what's going on in many cases.
E.g. it knows that `1 / 3` results in a `double`, which has a `toStringAsFixed` method, which returns a `String`, which has a `length` getter, which returns an `int`.
So, if you accidentally write "lenght", the editor will tell you about your mistake right away. Well, you won't even make that typo in first place, because you'll just auto-complete that word.
Furthermore, if you store the value of `length` in a `var`, the editor will remember that it's actually an `int` and it will then notify you whenever you're doing something weird with it.
All of that works just because the libraries you're using are annotated.
I forgot to give the example of using "Enum-like" types instead of boolean parameters, because boolean parameters don't give enough information, so then you might need to pass something like MyClass.MYVALUE to a parameter rather than true or false.
So yes, there's much to like and dislike. Needless to say, there are those that need more type features from the language and toolset than it already offers, even if it's kind of hard because to come up with more if some of it needs to go into compiling down to JavaScript.
With more type features they would expect to have more features like real Enums or what-have-you.
Even "var" declaration starts to look awkward in a language where types are available. So things like "for (int i = 0; i < 10; i++) {}" are more natural than "for (var i = 0; i < 10; i++)". And that's only the tip of the iceberg.
When you start declaring things like "listenToMouseEvent(MouseEvent e, () {})" when using some event API, that's when it gets uglier still.
But yes, to each his own. JavaScript is still the leader in many ways, including some basic performance despite not the extra type declarations. They have really extracted nearly all of the performance allowed from JavaScript it seems. So performance alone is not what will differentiate these other languages from JavaScript.
The point I'd make about it here, though, which is generically reflected in today's part 2 article, is that it's a fine pattern in and of itself, but the reason I think it's a "distraction" is that it actually moves away from the [[Prototype]] chain entirely.
You can use it as a bespoke pattern for combining two objects, but it's a mistake/confusion to think this has anything to do with JS's underlying delegation mechanism. The same point is made about mixins, and polymorphism (which also requires implicit mixins).
I think the key idea is that JS is a prototype-based OO model. It's easy to forget because there are so many abstractions[2] that act like a JS is a static class based OO language.
[1] http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-genera... [2] http://coffeescript.org/#classes
1. Prototype
2. Delegate
3. Mixin
4. Module
Understand how those patterns work and you should be able to understand how the libraries vary.Might also help: http://stackoverflow.com/questions/1645027/popular-javascrip...