Hopefully this will clear the whole "class" thing in javascript up with some people. Every time I mention using classes in Javascript I get the obligatory response, "That's stupid, they are not actual classes, just syntactic sugar. I'm not using that!"
face palm
Yes, I know...but look at how much nicer that is to type than his first example...
It's a Faustian bargain. It blesses Javascript with a wider audience, less initial balking by new JS programmers, and a few characters for established JS programmers. But it does so by deliberately misleading programmers by naming the syntax to be the same as the thing they think they want, but JS doesn't contain. That seems like a dick move that condemns a whole slew of new developers to permanent misunderstanding of what they're writing, and a fundamental lack of clarity on prototypical inheritance.
I will use it. Because syntactic sugar in this case does save some work. But I'm very conscious the code has swapped superficial clarity for deep lies.
I suspect Stack Overflow will overflow with even more 'javascript OO is broken' questions and the MDN article won't make more than a dent on clearing up that (deliberate) confusion.
There's no reason to confine "class" to just mean C++/Java style inheritance, just because it was used that way first.
Besides, most people used prototype based inheritance in this exact manner, so it's good to codify and give a concrete implementation, instead of 200 ad-hoc remakes of the same thing.
Third, people still get all the prototypal flexibility anyway if they want it.
I think that's a good argument for including the syntax, though it raises me from 'anti' to 'ambivalent'. But yeah, figuring out random coder's favourite OOP implementation style is an annoyance. Here Crockford seems to be a bit part of the problem: he seems to recommend a whole new approach every couple of years. His recommendations to never use 'new' (and now, don't use 'this') make it a pain to use other people's objects.
I wonder if a better consensus might be the best of all worlds.
Indeed, the problem is on code bases or teams with multiple ways of doing the same thing. I define new objects with.
var foo = Object.create(bar);
You do
var foo = new Bar();
Amy does
var foo = Bar();
and Ellen does
var foo = Bar.create();
For many values of 'better', the advantage is much smaller than the additional burden.
Thus I have no problem with him finding better ways, but his influence and his way of presenting things as the one best solution causes issues, in my experience.
> That seems like a dick move that condemns a whole slew of new developers to permanent misunderstanding of what they're writing, and a fundamental lack of clarity on prototypical inheritance.
Do you feel the same way about Python's use of the word 'class'? Because Python also uses prototypical inheritance under-the-hood. The abstraction is clean enough that most people don't know the difference, but it still really is "just" prototypes down there if you peel back the layers.
Python didn't used to have such flexible classes. It has migrated that way over time. So 'dick move' doesn't seem to be a valid criticism, to me.
That said, Python is a hybrid under the hood. It has become more prototypal over time, but still enforces the distinctions between classes and instances.
I personally think that the opportunity to fundamentally open Python to a better object model was missed when Metaclasses were added. I like Metaclasses in Python, I think they need to be there, and help all kinds of tasks. But it might have been better still to support full prototypal inheritance at that point.
The complaint I'm making is definitely true of Python though, because it dresses its objects in the outfit of classes and instances, few programmers end up understanding the underlying model and how it can be used to improve their code. And, because of that, it is unwise to use those techniques in code that will be written and maintained by a team. I tend to get nervous of code that uses custom Metaclasses, for example, because of the burden of education is imposes on a project.
I'd prefer a situation where Prototypal inheritance was actually taught as the Javascript approach. But perhaps that is utopian and naive.
The flexibility of prototypical inheritance is one of JavaScript's greatest assets. JavaScript: The Good Parts includes several different approaches to OOP which prototypes make possible.
`class` just picks one and pretends the rest never existed.
I don't see what's wrong with that. Converging on one way of doing things is part of the purpose of a standard. We still have the option of implementing a different method if we prefer, just as much as we ever did.
If it's truly a standard, there should be no other options.
Instead, this sets the stage for lots of OOPish JS which feels very different from a lot of existing code out there, and will result in awkward shims to interface the different paradigms.
> If it's truly a standard, there should be no other options.
That's a bit strong. Most languages come with a standard set of data structures, but that doesn't mean they make it impossible to implement other data structures if it suits you (e.g. few languages include binary trees as a standard data type, but that doesn't mean you can't use them).
C includes standard string algorithms, but that doesn't mean you can't write your own — you can even create your own string type if C strings are problematic for some reason.
Many things in many languages are there to offer a reasonable default that is generally useful. This seems along those lines to me.
>If it's truly a standard, there should be no other options.
That has never been the case with most standards. No see why start now. If it's a standard, it should just be present to all engines, and (but not necessarily) the most popular option.
Nothing about it being a standard necessitates it should be "the only option".
>Instead, this sets the stage for lots of OOPish JS which feels very different from a lot of existing code out there
Quite the opposite. This sets the stage for finally having code that looks THE FUCKING SAME as other code.
Instead of 200 ad-hoc implementations of the same exact OOP pattern -- which is what we have now.
Is his entire complaint that the syntax looks too much like Java? The fact is, people are already using prototypes to share common methods across objects, and classes are really nice short syntactic sugar for doing that. His comment on classes there is too flippant to even know the meaning of his complaint.
Beyond that I'd argue that classes give you more guarantees about how an object will behave at the expense of flexibility, although they give you escape hatches if you like. The reduced flexibility is really nice because you can define safe subsets of the language in which tools that analyze and refactor code are possible, even across files if you're using the module system. Moving the language in a direction where consistent universal tooling is possible to write is something that I love about classes, and is often overlooked in discussions about it. I don't care that classes are just a simplified subset feature of what's possible using prototypes, it covers the common case well enough and makes it possible to write really nice code completetion and refactoring tools that don't randomly break. That's worth all the extra magic in my opinion.
People do weird metaprogramming things with the existing constructs. In a lot of cases it's because people prefer to keep things DRY, but it hurts when you're trying to grep through a large codebase to find that one method you need. A contrived example:
It's impossible to statically infer what methods are being declared in ES5 code in general. You can pick up on some common assignment patterns, but not all the weird stuff people do. Just check out the top 10 NPM modules and I guarantee you'll find some odd assignment patterns. I can remember that the "colors" module did meta stuff when I last checked. You can't just easily run the code to find out what the module is exporting either, because of potential side effects from IO. You can wrap Node's core IO modules, but that solution is specific to Node, and still won't cover every possible case. In short, it's a huge pain to write tools to pull information out of Javascript code in general, and that hurts the tooling ecosystem around Javascript.
With the class pattern you at least have some staticly inferable information you're sure you can pull out of the class, and weird stuff is discouraged. I know in some ways that this is a weak argument, because of the new square bracket assignment, and the fact that anyone can muck with the prototype of the class after it's created. Even still, for the simple OLOO case it does encourage people to write code that has method names that can easily be staticly inferred, and that's a good thing for JS tooling possibilities.
I really just want omnicompletion that doesn't randomly break on me! The OLOO pattern is one that's incredibly common in JS
I think Crockford is wrong in that linked article. What he presents as unique features of prototype-based OO seem to really be features of highly dynamic OO. All of the things he implements in that article could be implemented just as easily (indeed, in more-or-less the same way) in Python or Smalltalk, couldn't they?
This is unrelated to JS classes, but is there a good reason for using getters/setters for some properties but not for other? I've always had trouble understanding why some people prefer this style. For instance, given the article's Circle definition (any of them), accessing a circle's radius is different than accessing its area:
let c = new Circle(10);
c.radius // => 10; good
c.area // Nope, that returns a function.
c.area() // => 314.15... OK
c.radius() // Raises a TypeError, 10 is not a function
Presumably, the author declared the area accessor as a function instead of a getter because it does some other computation beyond returning a stored value. But a different implementation of Circle could instead store the area and compute the radius from that[1]. In that case, would we then have to change the way of accessing the circle's properties to `c.area` and `c.radius()`?
I think it'd be much better to define these properties so accessing them has a uniform syntax: either `c.area` and `c.radius`, or `c.area()` and `c.radius()`[2]. Isn't in fact the purpose of adding getters and setters to the language to allow users to define how a property is accessed?
[1]: That might make sense if it turns out that asking for a circle's area is much more common than asking for its radius, and it's done so frequently that it impacts performance; or if we prefer to have circles with nice round numbers for areas in our application and don't care too much about their radii.
[2]: Yeah, i know that there is a named principle for this. In fact i always thought that adding getters and setters was motivated by the Uniform Access Principle.
This is not a hard and fast rule and I have no idea if there is any pattern, but I reserve getters for trivial properties like `firstName + " " + lastName;`
If it does any non-trivial amount of work, then I always use a function. The reason for that is in codebases that I've worked on in the past, getters were heavily overused and people ended up writing code that while at a glance seemed entirely reasonable, it was actually doing an absurd amount of redundant work and was causing performance issues. I feel that requiring the `()` to invoke it as a function at least hints that "this does stuff" rather than hiding the implementation in a getter.
> I feel that requiring the `()` to invoke it as a function at least hints that "this does stuff" rather than hiding the implementation in a getter. Does that make sense?
It does. But don't you feel that making that distinction makes the code more inflexible for (arguably) little gain?
To put a concrete example, let's consider an "items left" property of a TODO list:
class TodoList {
// ...
itemsLeft() {
return _.count(this.todos, todo => !todo.done)
}
}
itemsLeft was defined as method because it's computed by iterating a list, so we deem it non-trivial. But let's say that later on we discover that a better internal structure for our TodoList is to have the unfinished and finished todo's in different lists, so our itemsLeft becomes "trivial":
If using getters is preferable for trivial properties, should we then change itemsLeft to be a getter and refactor the existing code that uses it?
Besides, there's the problem of having to draw a line between trivial and non-trivial code (e.g., "is string concatenation trivial?", "but it's O(n+m) on the lengths of the strings!", etc). Using always the same style for accessors (either plain-old methods or getters), regardless of how complex the underneath code is, frees us from having to make this consideration in the first place and also leaves the door open for changing an object's implementation without changing its public interface.
---
I feel this perception probably depends on the language background tough. E.g., in languages like Ruby you always interact with objects via messages, so you never have this distinction of whether something is computed or not from a the caller's perspective (or at least not in a syntactic level).
But it's weird that this distinction is made in JS though, as many of the classic JS APIs have computed properties that "do stuff", and quite a lot! (e.g., Node#textContent). My guess is that most likely it's a result of the language lacking user-definable getters and setters for such a long time.
I don't know the author's intention specifically, but I generally use a pattern of:
1.) If a value read/write, then it's a property via getters/setters
2.) If a value is computed, then it's a function
Specific to this case, area is 'generally' a computed value of other attributes of a 'shape':
c.radius() = 10 'Bad, overwrote the radius function
c.area = 314.15 'Ok, radius is settable from just area. But doesn't match the area signature of other "shape"-like objects.
let rect = new Rectangle(10, 30);
c.area = 200 'Bad, which do we change, width or height?
Working with classes in ES6 is such a relief. My only complaint is that if you save a method as a variable or pass it to another function, it'll just be a function with its own scope (`this` won't reference the class instance, but instead the method function).
Easily "fixed" if you remember to put `.bind(this)` everywhere, though.
Nope, you'll still need to use "use strict" if you're not in a class definition or a module. It's just that classes and modules are strict mode by default (and there's no way of changing that as far as I know).
I'm sad that they don't have executable bodies. That was a key feature of CoffeeScript classes for me, and not having it makes ES6 classes considerably less useful/interesting.
Not gonna lie, classes, destructing, TCO, and all these "engineering optimizations" are great... but what JS really, really, really needs is some sort of way to declare decorator macros. A great use case for these macros (especially in the browser) is in dealing with those god-awful deprecation warnings any given big-enough framework will eventually start spitting out after enough usage.
Right now, framework developers use console.warn which, as a function, is usually buried in the framework, and so gives stack-traces that are entirely useless to a developer using said framework (especially if the framework wraps console.warn in some way). A good clean way of declaring macros will allow JS frameworks to not be cancerous to maintain by making the stack-trace for deprecated user-code very easy to spot and correct.
And unlike features such as classes, generators, symbols, and even TCO that are reasonably achievable through clever engineering, macros flat-out have to be special-formed in. But their utility in making JS a much more maintainer-friendly language would go a long way to reducing the misery of us plebeian js users.
But decorators are just syntactic sugar around functions. You can have them now with;
var deprecated = function(fn) {
console.warn("Deprecated!");
fn.apply(this, arguments);
};
then
var oldMethod = deprecated(function() {
});
In ES7 you'll be able to call decorators with the new syntax:
@deprecated
function oldMethod {
}
But nothing stopping you writing modular code now. The complaint about 'I have to write console.warn everywhere' seems to have an obvious answer; 'no you don't'.
As for other issues with ES6+7, the new syntax trades a few characters for a lack of clarity about what is actually happening.
Thanks for writing it out, but you've actually illustrated exactly my complaint. When you call oldMethod console.warn will spit out the line number of the wherever in your framework you have the
var deprecated = function(fn) {
console.warn("Deprecated!");
fn.apply(this, arguments);
};
declared... but you (as the user of a framework) still have no real idea where the actual function you shouldn't be calling (the fn) lives in your code (especially since the function can be anonymous)... you just know that the framework is telling you you're using a deprecated function somewhere in the code you wrote.
What I really want, and what can't be achieved with your purely in-language solution (and it really is a great solution considering the constraints), is a special form for things like @deprecated such that it straight-up can capture not only the function it's decorating, but also meta-data about the function (e.g. where it's declared, the caller / callee, etc.)
but obv. isn't useful in real code at the moment because it is FF-only (afaik).
I'm generally quite nervous of 'special forms' in languages that don't allow you to define them yourself. Part of why scheme is so powerful is that most of it can be implemented in the same tools it gives you. I'd like to see that approach.
That's a validation of some of the other discussion in the thread. JavaScript gets a 'class' keyword, but it's really just syntactic sugar over prototypical inheritance. So there is some misunderstanding about what it does and doesn't do.
That said, it could have also been written as:
static set circlesMade(val) {
Circle._count = val;
};
which means the exact same thing, and would maybe help people to understand that "_count" is just like a static class property on the Circle "class"
Is JavaScript really a prototype-based language? In prototype-based programming, you create an object which has shared functionality (the prototype), and then create new objects by cloning that object. That's not how you implement shared functionality in JavaScript; in JavaScript, you implement shared functionality by delegating to another object. In other words, JavaScript objects work like Python, or Ruby, or Smalltalk (yes, unlike these languages JavaScript, until ES6, makes no language-level distinction between an object which is also a class, and an object which isn't a class, but that's just a matter of syntactic sugar).
The 'cloning' is abstract: it doesn't refer to an actual duplication. Typically code is delegated for performance, and data is copied or constructed. JS is prototypical.
You identify the key point: there is no distinction between classes and instances. Classes are first-class objects, that can inherit themselves, and so on, and so on. Some languages (like Python) find they need these extra levels and introduce 'meta-class'es, this is irrelevant in JS.
Also the last bit is why 'class' is a bad move in ES6. ES6 still makes no distinction between a class and a non-class. It still does not provide classes in the Classical inheritance mode. It is just syntactic sugar for deriving an object with prototypal inheritance. I can inherit a class from an instance, or classes from classes. All it does is bless the approach of using prototypes in ways that are recognizable from non-prototypal languages. The fact that most people will (incorrectly but understandably) assume the JS now has classes is why this is ill advised.
> You identify the key point: there is no distinction between classes and instances. Classes are first-class objects, that can inherit themselves, and so on, and so on.
Classes are first class objects in Python as well, and have their own inheritance hierarchy. (And so on, and so on.)
> Some languages (like Python) find they need these extra levels and introduce 'meta-class'es, this is irrelevant in JS.
JavaScript lacks metaclasses because it lacks the concept entirely… no? (Though perhaps you can take your FooClass and run it through a function, permuting it as desired, but then, you would still need to do that at every base-class's definition site.)
JavaScript programmers do opine often (in my opinion) that Javascript is "prototype -based", and not single-inheritance OOP. Yet an objects behavior in JS seems exactly that of Python; if I take an object, "obj", and access obj.some_attr, I:
1. In JS: look for the attribute on the object (getOwnProperty, essentially?)
In Py: look for the attribute on the object (in __dict__)
2. If not found, start
In JS: follow the prototype chain
In Py: follow the MRO
Python just gives me exceedingly better syntax to do it with, IMHO. (Which the new additions in the article help with — I'm referring to the article's first example, in good ole JS.) This is slightly different from Java and C++, which strictly separate the whole instance-data from class methods.
You can't inherit a class from an instance in python. Classes and instances of classes are distinct (though the semantics aren't equivalent, a class is described as an instance too, but of a metaclass, not of its parent classes). Both can only inherit from classes. Once you instantiate an instance, your inheritance is terminated.
I can derive any object from any other object in Javascript. An object can be treated as a class, a metaclass, or an instance. There is no language difference. That isn't just syntax, that is a deep part of the implementation and semantics.
Both languages are Turing complete, so of course one can build a system that does prototypal inheritance in Python, in fact it is rather simple, given Python's powerful delegation system. Just as you can create classes in Javascript. But there is a difference.
If you disagree, it may be worth you giving a definition of what you think prototypal inheritance is, as distinct from class-based inheritance.
> perhaps you can take your FooClass and run it through a function, permuting it as desired, but then, you would still need to do that at every base-class's definition site
You don't have FooClass in Javascript in the same sense. There is no metaclass in Javascript, because there is no concept of a class. Thus the restriction of the two-level ontology doesn't raise its head.
> follow the prototype chain, follow the MRO
This is the same for any class-based language. There is a delegation chain that goes from instance -> class -> parent class, etc.
So again, what do you think would make something prototypal? Do you think it is a meaningless term? Or just something fashionable to make Javascript sound important?
This isn't bashing Python, you can do plenty of stuff in python. But it does have a different model of object orientation to Javascript. Fortunately Python is flexible enough to subvert that if you need to. But that's not what the terms mean. It isn't the only language that is powerful enough to be able to implement alternate OO models.
"The 'cloning' is abstract: it doesn't refer to an actual duplication."
But it's not just abstract, it has real implications, because JavaScript objects share implementation of the delegated methods; that is, they behave just like objects in class-based languages.
Classes in Python, Smalltalk, etc, are also instances; it's just that these language has facilities to produce particular sorts of instances which are well-suited for being delegated to (and take some measures to enforce the use of these facilities), whereas traditionally in JavaScript conventions are used to distinguish those object which are delegated to, and those objects which are not. However, the distinction between objects which are delegated to (i.e., classes) and object which are not exists just as much in JavaScript as it does in with classical inheritance.
Of course all reuse is somewhat similar, but dividing things into separate categories (class, instance, metaclass) and enforcing rules on how they relate isn't a 'just', that's what class based inheritance is.
The issue of abstraction is that, you don't implement prototypal inheritance with copying. No language does. That's why delegation is such a core part of any prototypal inheritance. There are different points and ways that happen. But all inheritance is delegation, in practice.
Can you give an example of when a piece of code (i.e. not data) would be different if it were delegated vs copied?
Regarding why you're probably getting downvoted: your comment does not add much to the discussion. Quick one-liners, which tend to make the obvious jokes, are usually seen as noise, and get downvoted.
Yes, I know...but look at how much nicer that is to type than his first example...
Pretty good, straight forward write up though!