JavaScript classes are disappointing.
https://speakerdeck.com/anguscroll/the-politics-of-javascript

JavaScript classes are disappointing.

(Image credit: https://speakerdeck.com/anguscroll/the-politics-of-javascript)

Last month the TC39 committee, the group that steers the development of the JavaScript standard, EcmaScript (ES), finalized the syntax of ES6 classes, and much to my disappointment, the disconnect between classical inheritance and prototypal inheritance will remain for the foreseeable future.

Classes, which are a type of object, cannot inherit from objects using class syntax. Actually, classes are a type of function, which is a type of object, so by transitive property of equality and the power of greyskull, classes are objects (something about a chicken and egg, or something).

First, a point about ES6 classes: the entire goal of adding a class definition syntax to the language is to get everyone writing modular, reusable libraries in a way that makes them compatible. Unlike many other object-oriented languages, JavaScript doesn't have classes per se, but class-like functions called constructors can be built using the prototypal inheritance features provided by the language. As a result, a pile of different so-called "classical inheritance" libraries have emerged to encapsulate this capability. At LinkedIn we use the superb Fiber.js, but they all accomplish roughly the same thing: create a more concise definition syntax, and make inheritance easier to think about.

However, Fiber constructor definitions aren't necessarily compatible with any other library's "classes", which might rely on private instance state, closure or other goofy stuff stuck on to the prototype to work. Some go to extreme lengths to emulate class features from other languages, like static methods, visibility modifiers, and multiple inheritance, which often require equally extreme tricks to implement. Fiber, for example, sticks a reference to the base class' prototype on the subclass.

In order to unify all these approaches, TC39 has introduced classes to the next version of the language that should get us all on the same page. The classes they defined are merely syntactic sugar for features already available in ES5, which makes compiling the new syntax to something that runs in today's browsers a breeze using something like Traceur or Babel.

However, the new classes make inheriting from an object, the essence of prototypal inheritance, more difficult if you use this syntax. It requires the same kind of acrobatics we had to use to create classes in ES5 in order to use a feature that has been core to the language since its beginnings as a quirky little toy developed by a madman at Netscape who had been sleeping under his desk for a month.

In ES6, classes (which remember, are objects) can inherit from classes, but not other kinds of objects.

(link: https://gist.github.com/noyesa/9c0cf569fc6e254e0435)

Objects beget Objects

One of the most unique features of JavaScript compared to most other object-oriented languages is that rather than building classes that instruct the runtime how to create instances, you can will objects into existence and build them up using object-literal syntax or appending new properties to an existing instance. Once you've built an object, you can then defer to it from any other object to provide properties not found on that object, a form of differential inheritance that, while obscure, isn't all that different from classical inheritance.

This model exists at a more meta level than classes, which are a language for describing the shape of object. JavaScript only lets you describe objects, and then point them to other objects from which they receive missing bits of their implementation, which, when concatenated together, form the overall shape, but this shape never exists definitively. If it did, it would be called a class. When thinking about inheritance from this sense, I see classes as a subset of prototypal inheritance. You can express all the capabilities of classical inheritance with prototypes, and then some.

However, JavaScript failed miserably in its implementation, and so left most developers yearning for proper classes, which they got... twenty years later. I can wax poetic about other forms of code reuse like mix-ins or higher-order functions, but classes are here, and they're set to become the most popular way to reuse code in JavaScript, as they are in every other language

Losing Your Roots

JavaScript forgot what it was before it made it out the front door. Back when the language was created, it was an amalgam of different concepts from Scheme, Self, and of course, Java. In an effort to look and act more like Java, constructor functions were added to the language as well as the "new" keyword for creating instances, which was entirely redundant except for the fact that it was the only way to achieve prototypal inheritance in early versions of the language. This did an excellent job of confusing the point of prototypes, which the community struggled with for years. The main entry-point for doing prototypal inheritance was designed to look exactly like creating the instance of a class in Java, but it was neither a class nor Java.

In the years following, the TC39 committee has created two recommendations to the JavaScript standard: ES3 and ES5; and with each, it has exposed the core of JavaScript inheritance through methods like Object.create, which builds prototypes in an explicit, declarative way. Also, the initially proprietary __proto__ property made its way into the specification, allowing you to change the prototype of an existing object.

And now with ES6, classes have been added to the language, but strangely, in a way that doesn't allow them to inherit from objects.

All the movement toward creating viable alternatives to classes for code reuse has seemingly been ignored by this new syntax. While it's still very much possible to accomplish this, the fact that the opportunity wasn't taken to actually fix the broken syntax of prototypal inheritance in JavaScript feels like a sorely wasted opportunity. Instead, the prototypal nature of JavaScript is buried, despite this syntax still relying on prototypes to work.

If you were trying to bend JavaScript to have classes previously, you're fine. If you were trying to use prototypes the way the language has intended for most of its existence, you'll find yourself writing code to bend those prototypes into classes to make them work with the new version of the language. They'll still technically work if you don't, but if you want to be on the classical inheritance boat, you'll need to do some work of your own.

(link: https://gist.github.com/noyesa/dbd585cec008b16a71de)

This is the only declarative way I've seen devised for using mixins in the new class syntax. The value passed as the right hand side of the extends operator must be a function that can be invoked with the "new" keyword, which precludes it to classes. Alternatively, you could do this:

(link: https://gist.github.com/noyesa/5ab681eeda9d9a0d39c8)

Note that this extends the prototype in a way that makes sense in non-classical JavaScript, but it does so outside of the new class syntax, and the "extends" keyword is never used. Not to mention, the Object.setPrototypeOf function causes the JavaScript engine to throw away the hidden classes that back this type, making this potentially costly.

Also worth pointing out: using Object.create the way you'll see it in pre-ES6 examples will cause problems:

(link: https://gist.github.com/noyesa/c220fced466d6f44165e)

Because the prototype has been completely replaced, callBaseSayHello is no longer a property of instances of Subclass.

I am not opposed to classes in JavaScript; I think classes are a succinct way of creating many kinds of relationship models. But ignoring that which makes up the base of the language in order to create something that finally looks like every other language is a step in the wrong direction.

Many have looked to alternative forms of code reuse, and found concepts more powerful or appropriate than classes to achieve it. This new class syntax pushes that implementation outside the class definition, and the clients of that code need to read between the lines to figure out what's going on, and for that reason, I find ES6 classes to be a disappointing step in the wrong direction, against the current of years of progress.

Johnathan Leppert

Engineering Leader | Software Engineer | Startup Founder | Advisor

9 年

One of the nice things about javascript is exactly this lack of classes. You aren't forced into some classical hierarchy or down some path of endless abstraction. Instead, you're encouraged to keep your implementation and object hierarchy the same. The only thing I want to see in javascript is universal CommonJS module support. Let us run our node modules in the browser, natively, without using something like browserify. You can keep the visages of the classical OOP languages and all the technical debt they create.

Lee B.

Frontend Engineering Manager ? Tech Lead ? Ex-LinkedIn

9 年

Awesome article Andrew and a great read. Probably an article in its own right, but there is some value in constructors and the use of syntax for object creation: Constructors are a familiar and concise way to have every object determine its initial value. Objects having an impartial?state at the time of definition is generally considered poor practice in OOP development. Its not that objects require?values to be set at initialization, but that objects should?have a complete state at the time of creation. For example, when we create a new car, is the car done if it has no doors? No. Setting the value of an object’s fields at initialization is giving consumers of your objects a clear understanding of what an object looks like?at that time in its lifecycle. The intent is that, once?initialized, the object is ‘ready to use’. By eliminating a constructor, you are making the interface of your object ambiguous, negatively affecting the clarity around an object’s polymorphic capacity and?inheritance chain.? Yes, you can still use?inheritance, but you’re telling other developers very little about the intentions of the object you are defining to be instantiated?and putting the burden of creation and inheritance upon the consumer.?This is particularly unwarranted in the case where you want to place some restriction around the consumption of the object. One more reason you create with a constructor is to inform the world about dependencies, a class needs to do it's job. Anyone, by looking at your constructors, should be able to figure out, what they needs in order to use this class. The constructor syntax is semantic?and clear - we are creating a new instance of an object, implying there can be more than one instance of the object and that it was intended to do so. This also allows for more simple patterns other than the constructor pattern, to become more clear.

Fabian Marino

Software Engineer at idealo internet GmbH

9 年

Javascript is losing its essence.

Jakub Szubarga

Systems Engineer

9 年

So now, we are waiting for ES7.

要查看或添加评论,请登录

Andrew Noyes的更多文章

社区洞察

其他会员也浏览了