ES6 in Depth: Classes
hacks.mozilla.org
hacks.mozilla.org
Yes, I know...but look at how much nicer that is to type than his first example...
Pretty good, straight forward write up though!
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.
I agree with Crockford on this.
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.
you make that sound like a negative. If you find a 'better' way, why not use it?
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.
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.
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.
`class` just picks one and pretends the rest never existed.
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.
We already see such ugliness in C++.
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.
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.
Lua is another well know language that has prototype-based inheritance.
I am very grateful to Crockford for the "Good Parts" book and JS5 - I like that lean approach.
Are Python classes broken as well?
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.
function Animal() {}
function Cat() {}
Cat.prototype = Object.create(Animal.prototype);
(function(fields) { fields.forEach(function(field) { Cat.prototype[field] = function() { // Some nonsense }; }); })(['what', 'the', 'hell']);
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
Is Crockford's dislike still based on this: http://www.crockford.com/javascript/inheritance.html ?
Because I don't quite buy into that line of thinking. I'm really looking forward to webassembly…
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.
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.
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": class TodoList {
// ...
itemsLeft() {
return this.unfinishedTodos.length
}
}
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.
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?Easily "fixed" if you remember to put `.bind(this)` everywhere, though.
myMethod = (arg) => {return this.value + arg;}
With es7.classProperties in babel.
https://babeljs.io/docs/usage/experimental/ myMethod = arg => this.value + arg; const typeMap = {
'i-text': this.processText.bind(this),
'image': this.processImage.bind(this),
'rect': this.processRectangle.bind(this)
};
Really annoying if you ask me, though I understand why it had to be this way.So there is a new way to activate strict mode?!
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.
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.
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.)
Having just a deprecated macro seems very narrow and special case to me.
Something like FF's getSource would work.
var deprecated = function(fn) {
console.warn("Function "+fn.name+" deprecated (at "+fn.getSource()+")");
fn.apply(this, arguments);
};
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.
static set circlesMade(val) {
this._count = val;
};
What's the value of "this" here?Something like:
Function Circle(){}
Circle.circlesMade(val) = function circlesMade(val){
this._count = val;
}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"edit: Why the downvote? I'm just having a bit of fun.