How does Babel.js create compile a class declaration into ES2015?

The ES6/Babel class syntax replicates the same prototypal pattern that you would get if you were using function constructors and overwriting the prototype. The examples below will result in the same instance object once calling the constructor.

ES6/Babel

class ComponentA {
      constructor(){
        this.property = "Proppy";
      }
      doStuff() {
        console.log("doStuff");
      }
    }

var c = new ComponentA();

ES5

var ComponentA = function () {
  this.property = "Proppy";
}

ComponentA.prototype.doStuff = function () {
  console.log("doStuff");
}

var c = new ComponentA();

Below is an example of how to inherit the prototype of another constructor in both ES6 class syntax and in ES5.

ES6/Babel

class Parent {
  constructor(){
    this.property = "Proppy";
  }
  doStuff() {
    console.log("doStuff");
  }
}

class ComponentA extends Parent {
  constructor() {
    super();
  }
}

var c = new ComponentA();

ES5

var Parent = function () {
    this.property = "Proppy";
}

Parent.prototype.doStuff = function () {
    console.log("doStuff");
}

function ComponentA () {
  Parent.call(this);
}

ComponentA.prototype = Object.create(Parent.prototype);
ComponentA.prototype.constructor = ComponentA;

var c = new ComponentA();

In the example you've given, your ComponentA is not inheriting the prototype of UltraFunctions() in the same way because you are not resetting the ComponentA.prototype.constructor back to the ComponentA function after overwriting it's prototype.

The result is a ComponentA instance object which in not really an instance of ComponentA but instead it's an instance of UltraFunctions() that was mutated by the ComponentA constructor.

You may have some negative results since a number of Object.prototype methods like .hasOwnProptery are not going to work the way they used to once you migrate your components to use the class syntax.

The other issue with your example is the "constructor" is misspelled. Which will cause the result in Babel to be different than what you may be expecting.


I just want to discuss how Babel.js converts a class to ES5 Javascript.

Babel uses a lot of helper functions, or I'd say "just look at the transpiled result." :-)

With ES2015, it's a really simple mapping, because the class syntax was deliberately kept really basic for this first version (ES2016 was going to extend it, but the proposals¹ didn't quite make it so they'll be later, probably ES2017 ES2018 ES2021 or ES2022).

class allows us to define:

  • The constructor function (via class and constructor)
  • The constructor function's prototype object's prototype (via extends)
  • Methods to put on the constructor function's prototype object
  • Methods to put on the constructor function itself (static)
  • A means of referencing the base "class" constructor and its prototype information concisely and portably

So this:

// Base "class":
class Base {
    // The code for `Base` goes in this special `constructor` pseudo-method:
    constructor() {
        this.baseProp = 42;
    }

    // A method to put on the `prototype` object (an "instance method"):
    baseMethod() {
        console.log(this.baseProp);
    }

    // A method to put on the constructor (a "static method"):
    static foo() {
        console.log("This is foo");
    }
}

// Derived "class":
class Derived extends Base {
//            ^------------------ defines the prototype behind `Derived.prototype`
    // The code for `Derived`:
    constructor() {
        // Call super constructor (`Base`) to initialize `Base`'s stuff:
        super();

        // Properties to initialize when called:
        this.derivedProp = "the answer";
    }

    // Overridden instance method:
    baseMethod() {
        // Supercall to `baseMethod`:
        super.baseMethod();

        // ...
        console.log("new stuff");
    }

    // Another instance method:
    derivedMethod() {
        this.baseMethod();
        console.log(this.derivedProp);
    }
}

becomes what we might write in ES5 (if we didn't use any helper functions) like this:

// This combines the name defined by `class` with the code defined in `constructor`:
var Base = function() {
    this.baseProp = 42;
};
// The "instance" method:
Base.prototype.baseMethod = function() {
    console.log(this.baseProp);
};
// The "static" method:
Base.foo = function() {
    console.log("This is foo");
};

// The derived constructor
var Derived = function() {
    // Call super constructor (`Base`) to initialize `Base`'s stuff:
    Base.call(this);

    // Properties to add when called:
    this.derivedProp = "the answer";
};

// This was done by `class` and `extends`:
Derived.prototype = Object.create(Base.prototype);
Derived.prototype.constructor = Derived;

// Overridden instance method:
Derived.prototype.baseMethod = function() {
    // Supercall to `baseMethod`:
    Base.prototype.baseMethod.call(this);

    // ...
    console.log(this.derivedProp);
};

// Another instance method:
Derived.prototype.derivedMethod = function() {
    this.baseMethod();
    console.log(this.derivedProp);
};

Items of note above:

  • constructor becomes the constructor function
  • All non-constructor, non-static methods become prototype methods
  • static methods are assigned to properties on the constructor function
  • Properties are just properties as usual
  • Creating the object to put on the prototype property of a derived constructor function is done via Object.create(Base.prototype), not new Base().
  • constructor calls the base constructor as its first action.
  • Calls to the super's methods in the ES5 version (Base.prototype.baseMethod.call(this);) are cumbersome and error-prone, one of the great things about the new syntax

¹ Some proposals that will markedly extend class syntax:

  • Class Public Instance Fields & Private Instance Fields
  • Private instance methods and accessors
  • Static class fields and private static methods

As of January 2021, V8 (the JavaScript engine in Chrome, Chromium, Brave, Node.js, and others) supports all of the above. SpiderMonkey (in Firefox and others) and JavaScriptCore (in Safari) aren't too far behind.