ES6 transpilation via babel

I don’t think the wrappers can be removed easily, but it’s possible. Someone would have to maintain that against Babel. That’d be cool to write ES6 classes and have it transpile to pure ES5 with no wrappers. It’s totally doable.

I don’t see why

class Foo extends Bar {
  someMethod() {...}
}

can’t simply become

function Foo() {
    Bar.apply(this, arguments)
}
Foo.prototype = Object.create(Bar.prototype)
Foo.prototype.constructor = Foo
Foo.prototype.someMethod = function someMethod() {...}

(in the simple case)

That’s a lot of extra work, unless we have a conversion script that converts to pure ES5 classes.

1 Like

@gadicc This was really bugging me. So I was trying to figure out why your tests would be so far off. 17x on a call to an extended function just made no sense to me. :stuck_out_tongue: Transpiling should not have affected it like it did.

I see now that the first test over complicated the use of super to create a false positive test. I kept telling myself these two instances should be returning exactly the same code values and getting the exact same reference values in the parent class. The only slow down would have been in the instantiating of the classes, IF we were testing that case.

new test returning exact same values:
http://jsperf.com/es6-classes-super-es5-vs-transpiled/8

ES6 code

    class Base {
      constructor(data) {
        this.data = data;
      }
      getData() {
        return this.data;
      }
    }

    class Sub extends Base {
       constructor(data) {
         super(data);
      }
    }

ES6 Transpiled

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Base = (function () {
  function Base(data) {
    _classCallCheck(this, Base);

    this.data = data;
  }

  Base.prototype.getData = function getData() {
    return this.data;
  };

  return Base;
})();

var Sub = (function (_Base) {
  _inherits(Sub, _Base);

  function Sub(data) {
    _classCallCheck(this, Sub);

    _Base.call(this, data);
  }

  return Sub;
})(Base);

ES5 code

    function ES5_Base(data) {
      this.data = data;
    }

    ES5_Base.prototype.getData = function() {
      return this.data;
    }

    function ES5_Sub(data) {
      ES5_Base.call(this, data);
    }

    ES5_Sub.prototype = Object.create(ES5_Base.prototype);
    ES5_Sub.prototype.constructor = ES5_Sub;

###Instances

this.es6t = new Sub(5);
this.es5 = new ES5_Sub(5);

###Test Cases

this.es6t.getData();
this.es5.getData();

You will be glad to know, I also increased the performance of the ES5 code at the same time as the ES6 code and they should be performing about twice as fast as the first example test.

I am going back to writing ES6 in most cases :smile:

Had anyone tested traceur compiler vs Babel? Traceur-runtime may not have some of these perf problems.

I haven’t tested, but check out how Traceur transpiles a simple ES6 class. It seems like a lot of overhead.

I went ahead and rolled my own class lib too: http://npmjs.com/lowclass. :relaxed: I tried to make it as performant as possible, generating pure ES5 classes.

@talves, sorry, my test wasn’t 100% clear… the important super was the one inside the overriden getData, since that’s what we’re testing :> The one inside the constructor was actually superfluous and was left over from something else I was trying, but wasn’t counted in the test time anyway, since we weren’t timing instantiation.

In any case, I really feel I have to stress again, I am only talking about using ES5 for components and other high traffic functions. We can benefit from beautiful ES6 classes everywhere except for things that are running 10,000 times a second. Even though it’s quite a big difference, I don’t think it’s something you’ll feel anywhere else.

Super also was obviously one of the worst performing examples. I did link it before, but here’s the time comparison matrix including babel vs babel-loose vs traceur. http://kpdecker.github.io/six-speed/. (I added it to the resource section too).

One of the reasons why some things in babel are so slow is because of strict ES6 compliance. If you run it in “loose” mode, it will be much faster, but then when the day comes for native ES6, (potentially) some of the code you wrote before that did work, won’t work anymore. So I don’t think that’s the best route. ES5 will always work.

I’m going to see how easy it is to move the browser version/adoption conversation into a new thread, I think it’s an important topic: Browser support, share, usage, adoption, upgrades, etc

4 posts were merged into an existing topic: Browser support, share, usage, adoption, upgrades, etc

Edit: sorry this is an older post that I moved accidentally and now is here out of order since I moved it back :confused:

No, I was only looking at the running time of the method of an already instanced class.

We’d need to do some proper research into browser usage (including native webviews on mobile) to make a final call, but my main point was just that, if some optimization will solve some problem for us and is expected to land in “the next chrome-beta”, we should not assume that this fix is acceptable for all our users (i.e. our users’ users) most of whom probably won’t be keeping their browsers constantly up-to-date.

e.g. according to https://kangax.github.io/compat-table/es6/, no current browser (including google chrome beta and canary) has native ES6 class support… some do if you enable flags for experimental javascript (which is unrealistic to expect all users to do), and some implementations aren’t final. So I think we’ll still be relying on transpiled code for some time to come (there’s also the issue of intelligently detecting the browser environemt and serving raw ES6 if it’s fully supported).

Yeah, I said both things made sense, it was just annoying come from the more permissive ES5 :slight_smile: That said, great news about the ES6 Java-like properties; that’s exactly what I was looking for… inheritable properties that could be overriden per subclass or instance. Shot!

I think we can assume it will be at least as fast or possibly even faster since it’s more restrictive. On the performance grid, native ES6 is marked as “identical” for all browsers for classes. But as per above, I think it will be a while before we can bank on all our users (and their users) having native ES6 browsers.

Yeah, I just rolled my own for now :> It looks like this:

var subComponent = Component.extend({

   doSomething() {
     this._doSomething();  // easy syntax for super's method
   }  

});

It was quite handy because it let me do a bunch of other tweaks and per component code that would be an extra few lines per class otherwise.

I don’t know so much. Most end users won’t know things about “oh I need to upgrade my browser for ES6 speed improvements”. The whole idea of transpilation is the assumption that it will take users much longer to upgrade than developers. People are going to look at an infamous demo, or a product using infamous, and say “wow, this feels like crap… it doesn’t deliver”, not “oh, wait, let me upgrade my browser first”.

Again, I’m not saying don’t use ES6… my main point is for high traffic code (e.g. components) As per:

So let’s look at components again… Say 100 nodes with 5 components each, that’s potentially 500 updates a frame, and at 60fps, 30,000 updates/second. Even a 33% difference can make a huge different with these numbers. To be clear though, I’m not talking about load time, class definition time, or instantiation time (which as @talves correctly said, is addressed by proper pooling). I’m only talking about function calls.

Here’s a jsperf I did for the use of super() in ES6 transpiled classes:

http://jsperf.com/es6-classes-super-es5-vs-transpiled/2

To be clear, after definitions, the only thing I’m testing/timing is:

someInstance.getData();

which calls it’s super’s getData to return the result (actual test code available in the link).

In Chrome, native ES5 was 27.5x faster, and in Firefox, 56.4x faster. That is a pretty huge deal, and was very noticeable when I was profiling components. Apparently it has to do with ES6 strictness… there’s a more permissive option which is only 1.7x slower (Chrome) and 5x slower (Firefox) , which is still way too much in my mind for components, but also:

  1. It means you’ll be able to write code that will work transpiled but break in native.
  2. It means we’ll have to explain to our users to use a non-ES6 version of our code or to enable this option which affects all their code too (assuming they have a babel setup).

Obviously also affects anything components use, such as utility classes, and probably Node methods.

You might say “ok so don’t use super()”, but well, is our approach then “use ES6 classes but don’t use their features?”. Yes, although instead of super, we could do the prototype call ourself, this is still just similar to the babel-loose example where there’s still a lot of overhead from all the wrapped code (we’re still calling the prototype of an ES6 transpiled class).

And then we have to do this kind of research for everything we use. I just think it’s much safer to do components (and probably anything they use) in ES5. We’re going to be judged on performance, and I think we should assume most of our users won’t be using ES6 browsers by the time we have something out and for quite some time after.

@gadicc Don’t be sorry, it is a valiant point you are making. Although I disagreed with how you ran the test, I am starting to see ES6 “transpiled” classes as an unnecessary and maybe evil code sugar.

Here are two articles I will point to for my decision, until they are fixed either by going native or a better prototypical design in ES7. I have been going down the rabbit hole based on my belief that classes will solve other problems when in reality they are just creating new ones.

The Two Pillars of JavaScript
How to Fix the ES6 ‘class’ keyword

I agree with the following quote 100%. We know we can add performance to the statement when transpiled.

P.S. Don’t use super unless you enjoy stepping through the debugger into multiple layers of inheritance abstraction. 1

Even when ES6 classes are native, I don’t think that can be done, at least not yet (not without evaling everything really slooooooooowly). But, if a way to make it happen become reality in browsers, then we could stick with a single syntax if we do like in the following examples.

Use ES5 for components.

// --------------- Component.js - an ES5 class?

export default
Class ('Component', {
    // No constructor here, so it'll have a default one, the same as in ES6.
    someMethod() {
        let something = 'something'
    }
}, 'es5')

// --------------- SomeComponent.js
import Component from './Component'

export default
Class('SomeComponent') .extends ('Component', {
    SomeComponent() { // constructor
        this.super.call(this)
    },

    aMethod() {},
})

Nodes to ES6?

// --------------- Node.js - an ES6 class?

export default
Class ('Node', {
    someMethod() {
        let something = 'something'
    }
}, 'es6')

// --------------- SomeComponent.js
import Component from './Component'

export default
Class('SomeWorldlyThing') .extends ('Node', {
    SomeWorldlyThing() { // constructor
        this.super.call(this)
    },

    aMethod() {},
})

We can still do things just as well with ES6 classes. It might even force you write code that’s more readable and understandable, which is good in my opinion. For example, instead of using new or the lack of new to symbolize something, why not just use a module variable or a class method? F.e., here’s something in ES5:

new Something
Something() // does something else without the use of `new`.

And here’s something in Es6, which is more readable and easy to understand:

new Something
Something.create() // We have a factory

We can also have a self-referencing Pool class (in theory, haven’t ran this code):

// an ES6 pool class.

let pool = null // to contain a single pool, in the module scope.

// give the class to the user.
export default Pool
class Pool {
  constructor(initialCount) {
      if (pool === null) {
          pool = new WeakMap()
          pool.set(this, {
              instances: [],
              create() {
                  return new Pool()
              },
          })

          for (let i=0; i<initialCount; i+=1) {
              pool.get()
              let newInstance = pool.get(this).create()
              pool.get(this).instances.push(newInstance)
          }
      }

      let newInstance
      if (pool.get(this).instances.length < 100) {
          newInstance = pool.get(this).create()
          pool.get(this).instances.push(newInstance)
      }
      else {
          newInstance = pool.get(this).instances.pop()
      }

      return newInstance
  }

  recycle(trashItem) {
      if (pool.get(this).instances.length >= 100)
          pool.get(this).instances.shift() // recycling is full, empty some out first.

      pool.get(this).instances.push(trashItem) // put the new item in the bin.
  }
}
Pool.threshold = 100 // maximum size of the pool.

// create the pool during module load, before app code (before the UI) runs.
new Pool()

And here’s a class extending it (extending is only one possible pattern to use):

// Some possible way to use the pool.
export default
class Chocolate extends Pool {
    constructor() {
        let chocolateBar = this.super() // returns an instance, which is a reference to the new object created by the super constructor call.

        // does it work?
        console.log(this === chocolateBar)

        this.flavor = "milk chocolate caramel"
        return chocolateBar
    }

    isInMouth() { return true /* for now */ }
    turnToLiquid() { console.log('chocolate melted.') }

    melt() {
        if (isInMouth())
            this.turnToLiquid()
    }
}

@gadicc and @joe, I think “classical inheritance” is not where we should head when we can and should be looking into breaking out of that thinking and moving to “Prototypal OO”. I don’t think we should be trying to create a factory by using the class. Let’s just do it the way it should be done using closures and prototypical inheritance if we are going to do it at all. Some of the Willyboat devs tried to introduce it, but it needed to be adopted from the base code to really work well and create a good API.

Kyle Simpson had a great 3 part article series about objects with regards to javascript and constructors.

But this does not mean we abandon ES6. We only abandon ES6 class!

We can and should expose these methods to the consumers of the library (API) when it will benefit them.

Aren’t Classes just sugar for Prototypal inheritance? In cases where we are going to write

    function ES5_Base(data) {
      this.data = data;
    }

    ES5_Base.prototype.getData = function() {
      return this.data;
    }

    function ES5_Sub(data) {
      ES5_Base.call(this, data);
    }

    ES5_Sub.prototype = Object.create(ES5_Base.prototype);
    ES5_Sub.prototype.constructor = ES5_Sub;

we may as well write that in ES6 (when it’s native), since it’s basically the same thing. I don’t think we should entirely abandon ES6 class, as it’s a much beter and readable syntax for the same thing (with very minor implementation details, and suitable in most cases).

If we want to supply a factory function to the user, it might be better to just lowercase it:

imprt {createChocolateBar} from 'somewhere'

let chosoBar = createChocolateBar() // the factory internals can be stored in the module from where the factory function came from,

and not use the new keyword at all. We can totally do that, and it would be easy to read. We definitely don’t have to use classes for everything, but when we do, the class cases should be as readable and clear as possible. It’s cool we have the ability to use-new-or-not, but IMO that doesn’t mean it’s better. Let’s make our code base easy to understand right away, so that completely new programmers and advanced programmers alike can get started with our libs quickly.

I think of Prototypal OO in JavaScript as the way that JavaScript objects are implemented, whereas Classical OO is just a pattern that we can use withing the Prototypal implementation of objects. Classical a subset of what we can do in JavaScript. The ES6 class syntax is just the constructor pattern with nice new shiny syntax (which I absolutely love). We don’t have to use it for everything. We can still do things like make pure objects that inherit prototypically (no constructors, only factory functions), “concatenate” objects (f.e. using _.extend or the new Object.assign), or both (constructor pattern + mixins onto prototypes). See http://aaditmshah.github.io/why-prototypal-inheritance-matters/#toc_10

Let’s keep all of our options open. I think classes make sense for lots of things, but not necessarily everything (although we can implement everything in classes if we wanted to).

Bottom line: Let’s write easy-to-read, well-documented, simple code.

Yes, but that was just me showing the problem with going down that road of a bad pattern.

Hmmmm, if we use a purely concatenation approach, we’ll have the most speed (no need to look down prototype chains) since all properties are on a single object. I’m starting to think that for our purposes we might like to go down that path. Here’s the component example, with one small change:

// --------------- Component.js - a concatenative class?

export default
Class ('Component', {
    // No constructor here, so it'll have a default one, the same as in ES6.
    someMethod() {
        let something = 'something'
    }
}, 'flat') // a la crockford.

// --------------- SomeComponent.js
import Component from './Component'

export default
Class('SomeComponent') .extends ('Component', {
    SomeComponent() { // constructor
        this.super.call(this)
    },

    aMethod() {},
})

The downsides would be that methods can’t be extended (without introducing internal closures withing the Class().exnteds() calls, which would decrease the performance we’re trying to achieve), but user’s can still call methods from another class manually. Internally, this is how the class would be created:

// -------- lowclass.js
let bodies = new WeakMap()

...

// if called Class().extends()
Object.assign(classBody, superClassBody) // simple concatenation.

// a body stored for each constructor (since we're not using prototypes).
bodies.set(classBody.constructor, body)

classBody.constructor.body = classBody // for convenience to the user.
return classBody.constructor

...

// --------- SomeComponent.js
// child class
import Component from './Component'

export default
Class ('SomeComponent') .extends (Component, {
    constructor() {
        Component.body.constructor.call(this) // call the super constructor (despite having no prototypes)
    }
    someMethod() {
        Component.body.someMethod.call(this) // call the super method (despite having no prototypes).
        ...
    }
})

// We have the classical pattern, but implemented with concatenation!

I’m really liking this idea. We’ve just eliminated prototype lag in Components (which will be updating at 60fps). Concatenative classes will give us the most performance (we simply provide a comvenience though an API (like my Class().extends()) so that users can still do Classical OOP).

@talves By the way, in that article you linked, they use Backbone as an example while describing their debugging problem. That’s not Classical OOP’s fault, that’s Backbone fault. Backbone’s constructor pattern is flawed, convoluted, and fails at one basic thing: naming constructors. They’d have had much better time actually using ES6 classes.

So, the article is flawed in the sense that it’s describing ES6 classes using a flawed ES5 pattern. They are only half correct about having to search up the prototype inheritance, but trust me, debugging Backbone isn’t pleasant. Part of the problem (as he alluded to) also lies in the fact that the team had bad programming practices (working around bugs of a super class in a child class instead of just fixing the problem at the root (and that probably means a lack of proper testing)).

But hey, if we use the concatenative approach (which I’m totally down to do) then we’ve eliminated some room for poor practices! I’m so down. I’m going to update lowclass after Tuesday. I must now disappear until then.

Not sure the article is really flawed in the sense that he is showing why a “classical pattern” is not needed in javascript. Did you read the last article in the series? That is the one that brings it together and has nothing to do with the ES6 vs ES5. It is showing how “functions delegate up their [[Prototype]] chain” and why constructors cause a lot of complexity that does not need to be introduced.

All this being said, I still have to do all the performance testing of any pattern proposed! :stuck_out_tongue:

At first glance, the use of ‘new’ seems to be much faster 4x. :confused:

Yeah, I’m gonna implement the concatenative feature in lowclass soon and benchmark it. :smile:

What were you comparing it against?

What do you guys think about the idea of concatenation-based classes, but with a classical way of writing them? I really like the idea of writing classes using a single syntax/API, yet having the ability to specify what type of classes they are (easy to modify later if needs change).

Maybe we can also have class helpers to make things like pools, etc:

let Dog = Class ('Dog') .extends (Animal, {...}) // normal class

let Dog = Class ('Dog') .extends (Animal, {...}).pool(100) // pooled class, 100 initialized initially
// or
let Dog = Class ('Dog') .extends (Animal, {...}) // normal class
let PooledDog = Dog.pool(100) // pooled class, 100 initialized initially

The key is to look at extending (inheritance) performance most of all. There really is a huge hit using class and extends over Object.assign where one level is 50% slower in my first tests.

Oh, sorry. I compared it against Object.create().