最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How does Babel.js create compile a class declaration into ES2015? - Stack Overflow

programmeradmin0浏览0评论

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.

company.js.ComponentA.prototype = new 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.

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.

Share Improve this question edited Mar 5, 2018 at 16:45 halfer 20.4k19 gold badges108 silver badges201 bronze badges asked Mar 3, 2016 at 14:29 xetra11xetra11 8,83518 gold badges100 silver badges198 bronze badges 7
  • "What happens now when I analyse this Component after instantiating it is that I see two level of prototypes now." No, there's only one level. The first level you're seeing is the instance, not its prototype, exactly as it was with the old function-style "classes." – T.J. Crowder Commented Mar 3, 2016 at 14:34
  • I think you need to show more context. Using new to create the object for the prototype property of a constructor function, as shown in your "before" example, was almost always an anti-pattern, for one thing, so if you can show more of what you're doing and perhaps explain why new is being used there, and separately say why you think there are multiple levels of prototype being created by the Babel stuff, that would be useful. – T.J. Crowder Commented Mar 3, 2016 at 14:38
  • I'm sorry I miunderstood the first prototype! as @T.J.Crowder said 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! – xetra11 Commented Mar 3, 2016 at 14:48
  • I don't understand what you mean by "smashed." The code in constructor does exactly what the code in an old-style constructor function does: Puts the property on the instance. The new syntax introduces no changes to the in-memory layout of the result. – T.J. Crowder Commented Mar 3, 2016 at 14:50
  • sorry for using "smashed". I'm not always happy using pragmatic terms so i try to spice things up with funny synonyms. Please give me some time to visualize the differences of the component hierarchy we use and how Babel.js' result is clinching with that. But remember that I don't need a solution for the problem. I just want to discuss how Babel.js converts a class to ES5 Javascript – xetra11 Commented Mar 3, 2016 at 14:58
 |  Show 2 more comments

2 Answers 2

Reset to default 22

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.

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.

发布评论

评论列表(0)

  1. 暂无评论