Nicer Syntax

One of the nicer things that makes ES6’s class so deceptively attractive (see Appendix A on why to avoid it!) is the short-hand syntax for declaring class methods:

  1. class Foo {
  2. methodName() { /* .. */ }
  3. }

We get to drop the word function from the declaration, which makes JS developers everywhere cheer!

And you may have noticed and been frustrated that the suggested OLOO syntax above has lots of function appearances, which seems like a bit of a detractor to the goal of OLOO simplification. But it doesn’t have to be that way!

As of ES6, we can use concise method declarations in any object literal, so an object in OLOO style can be declared this way (same short-hand sugar as with class body syntax):

  1. var LoginController = {
  2. errors: [],
  3. getUser() { // Look ma, no `function`!
  4. // ...
  5. },
  6. getPassword() {
  7. // ...
  8. }
  9. // ...
  10. };

About the only difference is that object literals will still require , comma separators between elements whereas class syntax doesn’t. Pretty minor concession in the whole scheme of things.

Moreover, as of ES6, the clunkier syntax you use (like for the AuthController definition), where you’re assigning properties individually and not using an object literal, can be re-written using an object literal (so that you can use concise methods), and you can just modify that object’s [[Prototype]] with Object.setPrototypeOf(..), like this:

  1. // use nicer object literal syntax w/ concise methods!
  2. var AuthController = {
  3. errors: [],
  4. checkAuth() {
  5. // ...
  6. },
  7. server(url,data) {
  8. // ...
  9. }
  10. // ...
  11. };
  12. // NOW, link `AuthController` to delegate to `LoginController`
  13. Object.setPrototypeOf( AuthController, LoginController );

OLOO-style as of ES6, with concise methods, is a lot friendlier than it was before (and even then, it was much simpler and nicer than classical prototype-style code). You don’t have to opt for class (complexity) to get nice clean object syntax!

Unlexical

There is one drawback to concise methods that’s subtle but important to note. Consider this code:

  1. var Foo = {
  2. bar() { /*..*/ },
  3. baz: function baz() { /*..*/ }
  4. };

Here’s the syntactic de-sugaring that expresses how that code will operate:

  1. var Foo = {
  2. bar: function() { /*..*/ },
  3. baz: function baz() { /*..*/ }
  4. };

See the difference? The bar() short-hand became an anonymous function expression (function()..) attached to the bar property, because the function object itself has no name identifier. Compare that to the manually specified named function expression (function baz()..) which has a lexical name identifier baz in addition to being attached to a .baz property.

So what? In the “Scope & Closures” title of this “You Don’t Know JS” book series, we cover the three main downsides of anonymous function expressions in detail. We’ll just briefly repeat them so we can compare to the concise method short-hand.

Lack of a name identifier on an anonymous function:

  1. makes debugging stack traces harder
  2. makes self-referencing (recursion, event (un)binding, etc) harder
  3. makes code (a little bit) harder to understand

Items 1 and 3 don’t apply to concise methods.

Even though the de-sugaring uses an anonymous function expression which normally would have no name in stack traces, concise methods are specified to set the internal name property of the function object accordingly, so stack traces should be able to use it (though that’s implementation dependent so not guaranteed).

Item 2 is, unfortunately, still a drawback to concise methods. They will not have a lexical identifier to use as a self-reference. Consider:

  1. var Foo = {
  2. bar: function(x) {
  3. if (x < 10) {
  4. return Foo.bar( x * 2 );
  5. }
  6. return x;
  7. },
  8. baz: function baz(x) {
  9. if (x < 10) {
  10. return baz( x * 2 );
  11. }
  12. return x;
  13. }
  14. };

The manual Foo.bar(x*2) reference above kind of suffices in this example, but there are many cases where a function wouldn’t necessarily be able to do that, such as cases where the function is being shared in delegation across different objects, using this binding, etc. You would want to use a real self-reference, and the function object’s name identifier is the best way to accomplish that.

Just be aware of this caveat for concise methods, and if you run into such issues with lack of self-reference, make sure to forgo the concise method syntax just for that declaration in favor of the manual named function expression declaration form: baz: function baz(){..}.