Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

My current mission is to convert a JavaScript component ES5 to ES6 (compiled with Babel.js). Before using classes and Babel.js we prototyped to get functions from other components.

com.company.js.ComponentA.prototype = new com.company.js.utils.UltraFunctions()

Now when using Babel.js and turning ComponentA into a class

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

What happens now when I analyse this Component after instantiating it is that I see two level of prototypes now. The first prototype holds "property" - the second one, which is nested into the first one holds all function in this case "doStuff". That brings up problems with legacy components which should not be converted to classes (yet). Because these components are being put in through the second level prototype they override the prototype which holds the functions of the "synthetic" class compiled by Babel.js.

I am not asking for a solution. I just want to get sure if I am right with the assumption Babel.js converts the classes to ES5 JavaScript. Especially the fact of creating two level prototypes as mentioned above.

Update

I'm sorry I miunderstood the first prototype! as @T.J.Crowder said in the comments the first is the instance - therefore "property" is being smashed into the instance while the functions are being inserted through prototyping to the "first" level prototype. So, replace everything what I said with second level to first level and first level to instance.

like image 988
xetra11 Avatar asked Mar 03 '16 14:03

xetra11


People also ask

How does Babel JS work?

Babel is a tool that lets you write your Javascript code using all the latest syntax and features, and run it in browsers that may not support those features. Babel is a transpiler that will translate your modern JS code into an older version of Javscript that more browsers are able to understand.

How classes are compiled in JavaScript?

The process of natively implementing classes is what we call a syntax sugar. This is just a fancy syntax that compiles down to the same primitives that are already supported in the language. You can use the new easy-to-use class definition, but it will still lead to creating constructors and assigning prototypes.

How do you use Babel?

Simply add a "scripts" field to your package. json and put the babel command inside there as build . This will run Babel the same way as before and the output will be present in lib directory, only now we are using a local copy. Alternatively, you can reference the babel cli inside of node_modules .


2 Answers

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.

like image 199
T.J. Crowder Avatar answered Oct 19 '22 16:10

T.J. Crowder


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.

like image 39
Jesse Lee Avatar answered Oct 19 '22 17:10

Jesse Lee