How to fix the `class` keyword
eric at paralleldrive.com
Wed Mar 4 12:23:00 UTC 2015
I've already posted this on my Medium blog here:
catch on, but that’s a problem because it’s fundamentally broken in many
Now that it’s out there and people are using it, it seems like the only
logical way forward is to *try to make it better for everybody*.
do so without a constructor, it’s called a *factory function*. The new `
*class*` syntax *can’t compete with the power and flexibility of factories*
— specifically stamps, and object pools are not the only factory use-case.
There is a whole section on object construction in the GoF “Design
exist only to get around the limitations of constructors and classes.
See also: Three Different Kinds of Prototypal OO.
The bottom line: *Class doesn’t give you any power* that isn’t already
supplied by *factory functions* and the *prototypal OO* built into the
language. All you’re doing when you create a class is opting into a *less
powerfull, less flexible mechanism* and *a whole lot of pitfalls and pain.*
Is there any hope that the `*class*` keyword will ever be useful? *Maybe.*
Why should we bother?
Why don’t we just create a lint rule and move on?
one have plans to create an ESLint config that prohibits `*class*` and
share it as far and as wide as I can — and since I’m building a community
enough to have an impact.
*Classes could be useful*. What if we want to build abstractions on top of
it? What if we want to do more things in the language itself that could
benefit with `*class*` integration (such as built-in *traits*)?
*We could make these changes opt-in* by adding config to the class itself.
That would prevent breaking changes and hopefully make the whole community
happy. As it stands, we're just making people with classical inheritance
backgrounds happy -- *at least until they fall into one of the many
pitfalls ahead of them.*
*How to Fix `class`*
1. *Make class inheritance compositional* similar to the way stamps are
other words, change the behavior of `*extend*`, or *deprecate `extend`* and
replace it with something like a *`compose` keyword* that can *compose
any number of classes.*
2. *Deprecate `new`. *`*new*` violates both the *substitution principle* and
the *open / closed principle*. The `*new*` keyword is destructive
because *it adds zero value to the language*, and it *couples all
callers to the details of object instantiation*. If you start with a
class that requires `*new*` (all classes in ES6) and you later decide
you need to use a factory instead of a class, *you can’t make the change
without refactoring all callers.* This is especially problematic for
shared libraries and public interfaces, because *you may not have access
to all of the code using the class.* You may think it doesn’t do any
harm to just call a factory function with the `*new*` keyword, but the `
*new*` keyword triggers behaviors that change what’s going on when the
function is invoked. If you can’t count on the function to behave the same
way for all callers, you can’t predict what the software will do. That’s
3. Make sure that *`class` obeys the substitution principle* when you
switch from a class to a factory and vise verse. This is an important
point, because if callers are counting on any behavior or property of a
class, and you decide to change the implementation to a factory, you’ve
just broken the calling code. Additionally, if callers are counting on the
behavior of a factory, and you switch the implementation to a class, that’s
similarly problematic, though as it stands, *there’s no good reason to
switch from a factory to a class*.
The third point may be the most difficult, but if we can *catalog every
possible breaking change* here, there may be some hope for `*class*` in the
future, assuming we can get consistency baked into the language spec.
If we can’t fix these problems with `*class*`, we should push to *deprecate
the keyword entirely,* because as it exists today, *`class` is broken and
absolutely should not be used*
Catalog of substitution breaks
- *The behavior of `this`*. It always refers to the new instance in a
class constructor. In a factory function, `*this*` is dynamic, and
follows a completely different set of rules. Possible solution: *deprecate
`this`* and instead refer to the class or function by name. A major
drawback of this solution is that it would break `*.call()*`, `*.apply()*`
and `*.bind()*`, unless we *also change their behavior* to override the
function name reference.
- *`instanceof`*- IMO, *this is broken anyway* because it doesn’t do
what the name describes, and from a user’s perspective, *it flat out
lies* when you try to use it across execution contexts, or when the
constructor prototype property changes. Possible solution: *deprecate
I know I've raised all these issues on es-discuss before and basically been
told to go suck an egg, but we all want the same thing -- *a better
DNA. That's how `class` found its way into JS in the first place, even
though we already had something *much better: A very good system for
prototypal OO (object literals, prototype delegation, and concatenative
inheritance via dynamic object extension).*
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the es-discuss