Hacker News new | past | comments | ask | show | jobs | submit login
Object-oriented Programming in vanilla JavaScript (medium.com/shlominissan)
95 points by 1bytebeta on Oct 17, 2017 | hide | past | favorite | 55 comments



Some parts of this article are word-for–word copied from the MDN article on Inheritance and the prototype chain [0]

For example:

> While this is often considered to be one of JavaScript's weaknesses, the prototypal inheritance model is in fact more powerful than the classic model. It is, for example, fairly trivial to build a classic model on top of a prototypal model.

Although the MDN content is CC-licensed it behoves the author to be clearer about what is quotation and what is original content.

[0]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inhe...


I wrote a paper on Classical OOP with ECMAScript 3/5 back in 2012 during my work on (the then-not-GNU) ease.js, which goes into quite a bit of detail on how prototypes work:

https://mikegerwitz.com/papers/coope/coope.pdf

I also provide a lot of information on the implementation of GNU ease.js here:

https://www.gnu.org/software/easejs/manual/Implementation-De...


It's a good paper, but it really needs a more accessible, much less dense, layout, and some syntax highlighting.


There are much nicer patterns available to program in an Object Oriented (or rather JavaScript) style. In the following gist I demonstrate (using a simple container library) private, protected and public members (including methods and properties) along with setting each object up for correct prototypal inheritance.

https://gist.github.com/nomad-software/78be4f1dd429325c5ebed...

I would always start with this pattern for readability and maintainability sake and if performance becomes an issue switch to the `Obj.prototype.method = f()` form to avoid duplicating method instances.


Why symbol.iterator?


This lets you specify the default iterator for an object. Lets you use `for of` on the object just like you can with arrays, maps, strings, sets, and typedarrays.


Mozilla has a nice writeup:

https://developer.mozilla.org/nl/docs/Web/JavaScript/Inherit...

But note:

> The lookup time for properties that are high up on the prototype chain can have a negative impact on performance, and this may be significant in code where performance is critical. Additionally, trying to access nonexistent properties will always traverse the full prototype chain.


> The lookup time for properties that are high up on the prototype chain can have a negative impact on performance, and this may be significant in code where performance is critical. Additionally, trying to access nonexistent properties will always traverse the full prototype chain.

This makes no sense to me - surely every non-trivial JavaScript implementation has inline caching, and are not doing any kind of lookup at all in performance critical code?


Why not https://eloquentjavascript.net/? The 2nd edition is still good from my understanding.

The author is also trying to update it and is close to their funding goal: https://eloquentjavascript.net/3rd_edition/


Thanks for bringing this up, I didn't know they were looking for funding for a third edition.


On this theme of the joys of plain old unencumbered javascript, I did one titled:

Callback Heaven: Continuation Passing Style Patterns for JavaScript:

https://medium.com/@b.essiambre/continuation-passing-style-p...


The inheritance example is incomplete, as it should do `Editor.prototype.constructor = Editor;` after `Editor.prototype = ..`. Without this, `david.constructor` will be `User`, and a check like `david.constructor === Editor` will yield false. It's not a huge deal, since the class is usually checked with `instanceof`, but it shows that classes in ES5 are quite subtle.


Just use `class`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

If, for some reason, you need to support old browsers, you can transpile to ES5, ES3, stone tablets and similar.


Classes are just syntactic sugar. They use prototype under the hood. So maybe it's good to know what happen there.


   var MyApp = MyApp || {};
This is an anti-pattern, only use it for polyfilling. Use local variable scope!


I used this pattern when you had multiple files extending the same global state. For example when lazy loading based on the page you're on.

It can definitely be overused, which is probably why it's an anti pattern, but it had it's uses before webpack.


Here's how I do it:

  "use strict";
  if(window.hasOwnProperty("MyApp")) throw new Error("MyApp already exist!");
  var MyApp = {};
But I recommend using a module loader.


That's not exactly the same code as above but yeah, use module loaders.


I agree this is an anti-pattern; you get better namespace encapsulation by wrapping your code in an immediately invoked function.


The post seems to be missing composition. One way of doing it is like this:

  const foo = {
      someVal: true,
      methodA() {},
      methodB() {}
  };
  const bar = Object.assign({}, foo, {
      methodC() {}
  });
  // bar now has all the properties of foo, plus methodC


Unfortunately, Object.assign() isn't supported by any IE except Edge.

https://docs.microsoft.com/en-us/scripting/javascript/refere...


True and it also doesn't support const. But it's easy enough to polyfill and use var if necessary. Many js libraries already have a similar function.


And? Is IE really that common to support, even in enterprises, in 2017? In 2018? In 2036?

IE already requires twice the budget - no grid, no flex, no ES8, and maintaining polyfills for all these things (if they're possible) requires maintenance and slows down build time.

Even slow moving corporates have AD managed Chrome or Edge since half the products they purchase don't work with IE.

The only reason people keep supporting it is people keep making it a big deal if something doesn't work on an unmaintained browser. We have to draw the line somewhere.


Short answer is yes. Typically the requirement is IE 10 or 11 now, rather than 6 or 8 as it used to be. So progress is being made.


Do you have someone who'll double your frontend budget, or a situation where IE users that have no other browsers available are so lucrative it's worth doubling your budget, to support them?

Frontend is grid, flex, and ES8. Supporting the older equivalents is expensive and it's web developers, caught in a culture of "it's good to support outdated browsers" who are paying the cost themselves, not clients.


It doesn't take double the budget to support IE and the vast majority of frontend work isn't in grid, flex, and ES8.


It takes double the amount of effort (a conservative estimate) to use floats, clearfixes, overflow side effect hacks, wrappers and various other hacks for simple layout.

It takes double the amount of effort (a conservative estimate) for async.waterfall, endless .then() chaining, callback hell and various other techniques to work around lack of `await`

It takes double the amount of work (a conservative estimate) to do some kind of weird Flash thing because your browser doesn't support HTML5 clipboard.

...amongst a massive amount of other tech, all of which is supported on current browsers.

You don't see the cost since you're already paying it by avoiding current frontend techniques. But unless someone else is paying that cost you're giving away your time for free by supporting IE.

(Of course, you should support Edge amongst every other major browser)


I don't know any website that is using stuff like `await` in the frontend without transpiling it down via babel first.

For both the css and clipboard problems, there are loads of well known and supported libraries if supporting IE is in scope of your project.


Babel's been great, but currently most browsers don't need it - the only one that does is the topic of this thread.

Flex and grid are hard to emulate. So is clipboard.


[flagged]


> apparently doing a lot of googling finding the random stuff that IE11 doesn't quite support natively

Do you genuinely think the need to lay stuff out in a grid or manage callback workflows is exotic? Have you done any front end development?

> Your idea of IE11 support apparently revolves around people using the worst types of tech and coding practices

No. As written in the comment you're replying to, it revolves around the huge gulf between IE11 support for current tech when compared to Edge/Chrome/Safari/Firefox.

Some links:

From https://rachelandrew.co.uk/archives/2016/11/26/should-i-try-...

> "However, even where similar properties exist in the two versions of the specification, the capabilities of the older spec and implementation are very different to the new one. This means you can’t simply run Autoprefixer and consider the job done."

The flex implementation is similarly broken. https://github.com/philipwalton/flexbugs

http://caniuse.com/#search=await

The only reason I mention clipboard (despite being less common) is we use it on our own site and the cost of implementing a second implementation in Flash fix is ridiculous.


I did a write up on my blog [1] that attempts to explain how prototypal inheritance works.

That said, whenever I teach OOP in JS I have found that a lot of developers do not understand the prototypal nature of JS.

Another source of confusion is the `this` keyword, how it works, and consequently how apply/call (which this article uses) affect `this' inside the function (or method).

[1] - http://looselytyped.com/blog/2012/08/22/on-prototypal-inheri...


There are two things I have always wondered:

1) if there are people who come back to using inheritance AFTER doing programming with typed systems that support typeclasses (like Haskell). If so, would you able to articulate why you think inheritance is still a superior pattern?

2) if there were any use cases that could be solved using inheritance that could not be solved more elegantly without it?


> if there were any use cases that could be solved using inheritance that could not be solved more elegantly without it?

There are behavior mixins, that you can look at Django views as an example. There are interface implementations when you want to have a few almost complete standard ones.

Both are cumbersome to implement on Haskell. If you make sure to not use any widely used class, you can hack them out of some sets of hierarchical type classes with possible some overlapping. But leaving half-done stuff at your API to be filled by client code is never pretty there.


What are typeclasses if not inheritance? As someone without a formal CS background, a type with multiple typeclasses sure looks to me like a class inheriting from multiple interfaces.


> What are typeclasses if not inheritance?

Overloading not inheritance. It is closer to the notion of interfaces in traditional OO languages, but it's a mistake to confuse interface implementation as inheritance.

This is a common problem with traditional OO languages: they conflate inheritance with subtyping, ie. "Y inherits the behaviour of X" is not always the same as "Y can be used anywhere an X can be used" (subtyping). C++ has "private inheritance" to separate these notions now, but it's a kludge from this initial design mistake.

So type classes use subtyping, but not inheritance. Interface implementation is also closer to subtyping than inheritance.


OOP classes (well, their instances) combine data and functionality into a single "thing" (well, object). Inheritance is nasty because you are not just composing structures of data or interfaces of functionality but both at the same time.

When you separate data from functionality into separate "things", composing things out of them becomes nicer.

I'm not a Haskell programmer, but AFAIK type classes contain functionality, NOT data (state).

Example of what I'm talking about: Clojure's Protocols.


Shameless plug - here is my description of how inheritance works in JS. https://github.com/sAbakumoff/Pritle/blob/master/2.Inhertian...


Great article! A few things have caught my eye:

> MyApp.users = MyApp.user || {};

Is this a typo, or intentional? If it is intentional, can someone explain the thought behind it?

Also, in some examples Medium (or the author's text editor) seems to have changed apostrophes and quotation marks, this makes the examples harder to try.


  foo = foo || {}
"mark foo as being foo if it is already defined or as an empty new object {} if it is not"

It's just to not accidentally set a already defined foo as an empty object incase foo is already defined. Yeah... JavaScript...

Another common idiom:

  const PORT = process.env.PORT || 8080
The || (logical OR) can be used for setting a default incase the value before it is falsey (null, undefined, false...).


Why the “Yeah...JavaScript...”? The inline || is an incredibly common and useful feature in many languages. Also Op was just talking about the plural/singular difference.


You're right that we might use "short-circuiting" (AKA lazy) `||` in many languages to use a default value, but it doesn't usually work for variables.

For example, in Python:

    >>> foo or "default"
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'foo' is not defined
In Javascript, variables are either bound (e.g. with `var`, as a function argument, etc.) or else they're treated as properties of the `window` object (behaving effectively as globals).

This is very unusual, and can be thought of as mixing "data" with "code": the `window` object acts like a reified version of (global) binding environment, which in most other languages is implicit. This has implications for things like the usual distinction between bound and free variables (since we can imperatively add fields to `window` at any point), and hence for things like alpha-equivalence.


It will work for `foo = None`. It is just that undefined vars in Python are an error in a different class from a missing value.

For some reason, the short-circuiting is more used on languages where undefined vars is in the same class of missing values. But that reason is not obvious to me. Python in particular has an odd mixing of quasi-static and dynamic errors that works unreasonably well but does not accept this construction.


> It will work for `foo = None`

Yes, but in that case the variable is irrelevant:

    foo = None
    foo or "default"
Here the variable `foo` is 'just doing its job' and can be simplified away to get:

    None or "default"
This shows that variables aren't involved here: `or` is just an operation on data (in this case `None` and `"default"`), just like `+` or ``.

> It is just that undefined vars in Python are an error in a different class from a missing value.

Exactly, and I think that's a perfectly reasonable justifaction for disparaging Javascript (along with bash, PHP and anything else which has this similar behaviour).

In fact, as I said above, Javascript doesn't even have a notion of "undefined variable", since variables will be looked up in the `window` object if they're not locally bound, and hence what looks* like an undefined variable is actually (according to Javascript's semantics) a missing value (a missing property of the `window` object).

> Python in particular has an odd mixing of quasi-static and dynamic errors that works unreasonably well but does not accept this construction.

I'm not sure what you mean here. As far as I can tell, there are two ways to think about errors in Python:

- From one perspective, all Python errors are dynamic, since they happen at run time when we attempt to interpret the offending code. Some of these errors happen when/if we interpret an `import` statement, some happen when a more localised expression/statement is encountered.

- A different perspective is that Python doesn't actually have a concept of errors at all. There are values available in the language which are called "errors", but they're not really errors, in the same way that `myError = "hello"` isn't actually an error (it's a string, which might represent an error but it isn't a distinguished category of things; it's just a value). Since it's semantically meaningful to perform calculations involving these values (often, but not necessarily, with the control-flow statements `try`, `except` and `raise` (which is just another form of `yield`)) they're not "errors" in the same way that, say, a segfault or a compiler error is (which Python doesn't, or shouldn't, have).

For example, here's a perfectly valid (although inadvisable) way of defining `hello world` in Python:

    $ cat x.py
    def say_hello():
        try:
            foo()
            print "goodbye"
        except:
            print "hello"

    def foo():
        i_am_undefined

    $ python
    Python 2.7.13 (default, Dec 17 2016, 20:05:07)
    [GCC 5.4.0] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import x
    >>> x.say_hello()
    hello
    >>>


Its not the inline ||. It is the need for such a pattern in JS due to terrible scoping rules, such that it is very easy for a variable definition to leak somewhere else and you really don't want to crush it.


I think he meant users vs user.


Yes, sorry if that was unclear, I meant plural vs singular.


> MyApp.users = MyApp.user

it's a typo


Here's a fun tool for visualizing prototype chains in random code.

http://www.objectplayground.com


It all looks very tidy when you see it set out like that.


I love prototypical inheritence. I find it much tidier and easier to understand than the class based inheritence in other C family languages.

I really wish that class syntax did not become a thing in Javascript. IMO that was the biggest mistake made so far in it's development. Just call a spade a spade instead of piling another level of abstraction on top.


It added a bunch of other useful things, like getters/setters, constructor function, simpler method descriptions, etc.

I don't have a problem with the class title, after all it describes a class of things. When you extend something you really extend the scope of your class, so I still find it adequate.

There are differences in OO behavior between Java and C# but I haven't heard people complaining about naming classes classes.


That is because Java and C# use classical OO. Javascript does not, it uses prototypal OO.

Classes are static contracts that define the state and behavior of object instances.

Javascript objects are not contractually obligated to have the state and behavior of a 'class'.


I know that and probably that's why people feel a bit annoyed by the Class keyword. However it's really close to what classes should represent. https://en.wikipedia.org/wiki/Class_(computer_programming) first two paragraph matches the JS use case well.

> Javascript objects are not contractually obligated to have the state and behavior of a 'class'.

I don't know what you mean by "contractually obligated", but if I instantiate a class in JS then it will have a state and behavior I defined in that class. If your problem is that if I change that class later, then the instances will change as well, then yes, it works like that. Calling something a class in my mind is not strictly limited to classical OO, it's just a loose concept different languages implement differently.


I feel like this article is 30 steps backward instead of a few steps forward.


Plagiarized, isn’t it?




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: