The Problem with ES6 Classes

Author: Shehroze Khan


The ES6 update to JavaScript can improve the quality of life for many developers. Block scoping, arrow functions, default parameters, property short hands, and so many other things improve the quality of life for many developers. They even added classes so the Java, Ruby, Python, and C++ developers can bring their object oriented skills to the language.

Sounds great! How do I use it?

We are going to create this awesome software that models different pop stars and prints out their hits. Let’s try this in Java first before moving over to ES6.

    
      import java.util.ArrayList;

      abstract class PopStar {
          String name;
          PopStar(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        abstract String getHit();

        static class TaylorSwift extends PopStar {
            TaylorSwift() {
                super("Taylor Swift");
            }
            public String getHit() {
                return "Shake\nShake\nShake\nShake\nit\noff";
            }
        }

        static class Rihanna extends PopStar {
            Rihanna() {
                super("Rihanna");
            }
            public String getHit() {
                return "Work!Work!Work!Work!Work!Work!Work!Work\n";
            }
        }

        public static void main(String[] args) {
            ArrayList popStars = new ArrayList();
            popStars.add(new TaylorSwift());
            popStars.add(new Rihanna());
            for (PopStar popStar : popStars) {
                System.out.println(popStar.getHit());
            }
        }
    }


Now, we just need to port this over to JavaScript.

Actually JavaScript doesn't have classes

The word “class” in JavaScript is misleading. What ES6 introduces as classes is syntactic sugar over the prototype to mimic the classes found in other languages. Let’s dig a little into the implementation.

    
    var object = {}

When we create an object, it inherits its parent’s prototype. The highest parent is the empty object, as shown above. Every time we inherit from another object, the parent’s prototype is added. This creates a chain of prototypes all the way up to the default. When I access something from an object, it looks at the immediate object properties. If it doesn’t find it, it walks up the prototype chain to look for a match. ES6 mimics inheritance by adding some sugar to this process.

    
      var __extends = function (d, b) {
        for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
    PopStar = (function () {
        function PopStar(name) {
            this.name = name;
        }
        PopStar.prototype.getHit = function () {
        };
        return PopStar;
    }());

    var TaylorSwift = (function (_super) {
        __extends(TaylorSwift, _super);
        function TaylorSwift(name) {
            _super.call(this, “Taylor Swift”);
        }
        TaylorSwift.prototype.getHit = function() {
            return "Shake\nShake\nShake\nShake\nit\noff";
        }
        return TaylorSwift;
    }(PopStar));

It looks messy. ES6 is obscuring a level of understanding that developers should have when coding JavaScript.

But prototypes are complicated

In the Java example for our pop star program, we use abstract classes. We could also use a regular class and simply override the parent method. Maybe define some interfaces to group them logically. How about all three?

  
    import java.util.ArrayList;

    abstract class Person {
      String name;

      public Person(String name) {
          this.name = name;
      }

      public abstract String getName();

      interface Star {
          public String getHit();
      }

      interface Singer {
          public void sing();
      }

      class Performer extends Person {

          public Performer(String name) {
              super(name);
          }

          public String getName() {
              return this.name;
          }
      }

      abstract class StarPerformer extends Performer implements Star {

          StarPerformer(String name) {
              super(name);
          }
      }

      abstract class StarSinger extends StarPerformer implements Singer {
          StarSinger(String name) {
              super(name);
          }
      }

      static class TaylorSwift extends StarSinger {

          TaylorSwift() {
              super("Taylor Swift");
          }
          public void sing() {
             //I actually don't need this function for my app. If I was using prototypical
             //inheritance I would choose not to add it.
          }

          public String getHit() {
              return "Shake\nShake\nIt\nOff";
          }
      }

      public static void main(String[] args) {
          TaylorSwift taylorSwift = new TaylorSwift();

          //What type do I use for my list? Any of the following could potentially work
          ArrayList Persons = new ArrayList();
          ArrayList Stars = new ArrayList();
          ArrayList Singer = new ArrayList();
          ArrayList StarPerformer = new ArrayList();
          ArrayList StarSinger = new ArrayList();
          ArrayList TaylorSwift = new ArrayList();

      }
  }


The model can easily get too complicated for the problem at hand. In JavaScript, we are only dealing with key value pairs:

  
    var PopStar = {
      getName: function() {
      return this.name
    }
  }
  var TaylorSwift = Object.assign({}, PopStar, {
    name: "Taylor Swift",
    getHit: function() {
      return “Shake it off”
      }
  })


Object.assign copies the properties of each parameter into the first parameter. It is a clean way of doing inheritance without hiding the guts from the end user. It’s also a lot less code and straightforward: no interfaces, no abstract classes, just copy what you want as you need it. It turns out Lady Gaga is coming out with a new album soon. Let’s add her into our Java code.

  
    static class LadyGaga extends PopStar {
        LadyGaga() {
          super("Lady Gaga");
        }
        public String getHit() {
            return "Po po po poke her face po po po poke er face";
        }
  }


It seems like a lot of code just to replace one function and one parameter. In JavaScript, functions are just treated like any other object. We can pass them around as parameters and even return them.

Do we even need inheritance?

We don’t. If we wanted to have a collection of pop stars in Java and print out their hits, we need classes. Let’s use JavaScript’s dynamic typing and support of higher order functions to our advantage.

  
    function PopStarFactory(name, getHit) {
        return {name, getHit}
    }
    var popStars = [
      PopStarFactory("Taylor Swift", function() {
        return "Shake it off"
      }),
      PopStarFactory("Rihanna", function() {
        return "Work"
      })].map(function(star) {
        console.log(star.getHit())
      })


The biggest problem with ES6 classes is that it hides easier, cleaner, and more efficient solutions specific to JavaScript. By introducing classes, programmers with experience in object oriented design will gravitate to those solutions simply because they look familiar. ES6 classes promote AntiPatterns.