ponyfoo.com

ES6 Classes in Depth

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

Welcome to ES6 in Depth. Are you new here? You might want to learn about destructuring, template literals, arrow functions, the spread operator and rest parameters, or object literal features in ES6. Today is going to be about “classes” in ES6.

Like I did in previous articles on the series, I would love to point out that you should probably set up Babel and follow along the examples with either a REPL or the babel-node CLI and a file. That’ll make it so much easier for you to internalize the concepts discussed in the series. If you aren’t the “install things on my computer” kind of human, you might prefer to hop on CodePen and then click on the gear icon for JavaScript – they have a Babel preprocessor which makes trying out ES6 a breeze.

Onwards!

What do you mean; classes in JavaScript?

JavaScript is a prototype-based language, so what are ES6 classes really? They’re syntactic sugar on top of prototypical inheritance – a device to make the language more inviting to programmers coming from other paradigms who might not be all that familiar with prototype chains. Many features in ES6 (such as destructuring) are, in fact, syntactic sugar – and classes are no exception. I like to clarify this because it makes it much easier to understand the underlying technology behind ES6 classes. There is no huge restructuring of the language, they just made it easier for people used to classes to leverage prototypal inheritance.

While I may dislike the term “classes” for this particular feature, I have to say that the syntax is in fact much easier to work with than regular prototypal inheritance syntax in ES5, and that’s a win for everyone – regardless of them being called classes or not.

Now that that’s out of the way, I’ll assume you understand prototypal inheritance – just because you’re reading a blog about JavaScript. Here’s how you would describe a Car that can be instantiated, fueled up, and move.

function Car () {
  this.fuel = 0;
  this.distance = 0;
}

Car.prototype.move = function () {
  if (this.fuel < 1) {
    throw new RangeError('Fuel tank is depleted')
  }
  this.fuel--
  this.distance += 2
}

Car.prototype.addFuel = function () {
  if (this.fuel >= 60) {
    throw new RangeError('Fuel tank is full')
  }
  this.fuel++
}

To move the car, you could use the following piece of code.

var car = new Car()
car.addFuel()
car.move()
car.move()
// <- RangeError: 'Fuel tank is depleted'

Neat. What about with ES6 classes? The syntax is very similar to declaring an object, except we precede it with class Name, where Name is the name for our class. Here we are leveraging the method signature notation we covered yesterday to declare the methods using a shorter syntax. The constructor is just like the constructor method in ES5, so you can use that to initialize any variables your instances may have.

class Car {
  constructor () {
    this.fuel = 0
    this.distance = 0
  }
  move () {
    if (this.fuel < 1) {
      throw new RangeError('Fuel tank is depleted')
    }
    this.fuel--
    this.distance += 2
  }
  addFuel () {
    if (this.fuel >= 60) {
      throw new RangeError('Fuel tank is full')
    }
    this.fuel++
  }
}

In case you haven’t noticed, and for some obscure reason that escapes me, commas are invalid in-between properties or methods in a class, as opposed to object literals where commas are (still) mandatory. That discrepancy is bound to cause headaches to people trying to decide whether they want a plain object literal or a class instead, but the code does look sort of cleaner without the commas here.

Many times “classes” have static methods. Think of your friend the Array for example. Arrays have instance methods like .filter, .reduce, and .map. The Array “class” itself has static methods as well, like Array.isArray. In ES5 code, it’s pretty easy to add these kind of methods to our Car “class”.

function Car () {
  this.topSpeed = Math.random()
}
Car.isFaster = function (left, right) {
  return left.topSpeed > right.topSpeed
}

In ES6 class notation, we can use precede our method with static, following a similar syntax as that of get and set. Again, just sugar on top of ES5, as it’s quite trivial to transpile this down into ES5 notation.

class Car {
  constructor () {
    this.topSpeed = Math.random()
  }
  static isFaster (left, right) {
    return left.topSpeed > right.topSpeed
  }
}

One sweet aspect of ES6 class sugar is that you also get an extends keyword that enables you to easily “inherit” from other “classes”. We all know Tesla cars move further while using the same amount of fuel, thus the code below shows how Tesla extends Car and “overrides” (a concept you might be familiar with if you’ve ever played around with C#) the move method to cover a larger distance.

class Tesla extends Car {
  move () {
    super.move()
    this.distance += 4
  }
}

The special super keyword identifies the Car class we’ve inherited from – and since we’re speaking about C#, it’s akin to base. It’s raison d’être is that most of the time we override a method by re-implementing it in the inheriting class, – Tesla in our example – we’re supposed to call the method on the base class as well. This way we don’t have to copy logic over to the inheriting class whenever we re-implement a method. That’d be particularly lousy since whenever a base class changes we’d have to paste their logic into every inheriting class, turning our codebase into a maintainability nightmare.

If you now did the following, you’ll notice the Tesla car moves two places because of base.move(), which is what every regular car does as well, and it moves an additional four places because Tesla is just that good.

var car = new Tesla()
car.addFuel()
car.move()
console.log(car.distance)
// <- 6

The most common thing you’ll have to override is the constructor method. Here you can just call super(), passing any arguments that the base class needs. Tesla cars are twice as fast, so we just call the base Car constructor with twice the advertised speed.

class Car {
  constructor (speed) {
    this.speed = speed
  }
}
class Tesla extends Car {
  constructor (speed) {
    super(speed * 2)
  }
}

Tomorrow, we’ll go over the syntax for let, const, and for ... of .... Until then!

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (10)

Igor wrote

Hi Nicolas,

thanks again for a wonderful post. I have tried using the new ES6 features in a small resilience library for nodejs applications I wrote, and classes was one of the features which caused the most confusion for me. I totally agree, that the code looks way cleaner and is easier to maintain without all the noise. However there are two things, that I still keep thinking about:

  • I tried to find a way of making parts of the classes “private” (data and methods), but gave up eventually, because it made the code unreadable. Do you have any thoughts about it or is it something, that will come with ES7?
  • There is also a never ending debate about the new keyword. In my previous projects I was using the stampit library to generate Objects and to avoid deep inheritance by using its compose feature. With classes it seems to be easier to end up with even bigger mess. What is your opinion on this?

Thanks, Igor

Wei wrote

How do you add static properties? For example, I used to like being able to do:

function Foo() {

}

Foo.BAR = 'bar';

Now, it seems I still have to do this?:

class Foo {
  constructor() { ... }
}

Foo.BAR = 'bar';
Igor wrote

I did it the following way:

class HystrixConfig {
    static get metricsPercentileWindowBuckets() {
        return customProperties.get(
           HystrixPropertiesNames.HYSTRIX_METRICS_PERCENTILE_WINDOW_BUCKETS);
    }
}

In other classes I can just call

HystrixConfg.metricsPercentileWindowBuckets

The use case is a bit different though, because I am not returning a static value

Semigradsky Dmitry wrote

With class properties:

class Foo {
  static BAR = 'bar'
  constructor() { ... }
}
Scott Robinson wrote

Hi Igor,

Unfortunately you can’t make methods/data private in JavaScript. Everything a class/object owns can be found and read. ES6 Symbols will get you close, but even then you can use the Object.getOwnPropertySymbols method.

Now, if you don’t care about actual privacy and just want a way to indicate to the user/caller that it’s private, just prepend the name with a _, which seems to be convention.

Scott

Nicolas Bevacqua wrote

You pretty much can if you use proxies, as we’ll start looking into next monday :-)

Claire wrote

for some obscure reason that escapes me, commas are invalid in-between properties or methods in a class, as opposed to object literals where commas are (still) mandatory.

Not sure why this is ‘obscure’ - there’s no reason why you would expect commas. Just because something that looks vaguely similar uses commas, doesn’t mean everything will. Classes are not objects, methods are not properties. Methods inside a class are simply methods that only run in the scope of that class. They are not parts of the class in the way that properties are parts of an object.

Allen Wirfs-Brock wrote

Comma aren’t allowed because semicolon is the intended separator for elements of a class body. You can put semicolons between method definitions within a class body but it is not required. Just like a semicolon isn’t required at the end of function declaration within a statement list. The use of semicolon within a class body will become more apparent as additional kinds of class elements are added to class bodies in the future.

Farzad Qasim wrote

Nice definition, however curious to why you’ve compared super to C#'s base when super is an identical copy of Java’s super