Does that rely on the underscore meaning super?
Thatā be great to post onto the Babel GitHub issue tracker. If itās transpiling to ES5, it should only be marginally slower, one would think. EDIT: https://github.com/babel/babel/issues/2399
Yeah, right! Thatās not happening. x}
After seeing your findings, Iām totally down to go the pure ES5 route.
The loss of the satisfaction of how writing ES6 classes feels will be tiny compared to the gains in satisfaction from making the our library performant.
I never realized how under performing ES6 transpiling is today! I am kinda embarrassed that I never really thought about it until now. In truth, there has never been a need to look at it before now. Interesting to me is I even asked "why did WillyBoat not use ES6" to the willyboat team and not one answer from them.
We really need to think about how we are going to set this up to eventually go to the the faster code base between ES5 and ES6.
- Is it safe to use ES6 modules?
- should we still maintain an ES6 code base. Let the transpiler ignore the ES5 code and change when supported browser versions support native ES6?
- would it be too much work to create the ES5 code from the transpile by stripping the wrappers? Always having the ES6 version ready?
I probably know the answers to most of these, but just want to throw them out in the case someone has a better idea than we come up with.
Edit: I have to take back some of my concern, because I was basing it on the tests above. Please see my test below. There should be no unexpected slow down of an extended class if written properly.
I asked Michael Obrien in person one time, and he said he loves ES6, but that performance would be out of their control.
Good to know that was the reason. Makes me feel better they came up with the same conclusion. Another example of documenting and being VERY clear about decisions, so we can just link to the explanation and not have to explain it
I think so, because that only affects the code wrappers prior to any code running, so even if it takes a looooong time to load those modules, it wonāt actually affect the runtime performance of the app.
Yeah, I donāt see why not. Certain things should be just fine (ES6 modules for example). We can write classes in a forward-compatible manner by using ES5 classes and never calling the super contructor after the use of this
.
Which wrappers? The module wrappers? I donāt that really matters because they are all evaluated at the beginning (unless weāre using HTTP/2 with JSPM, but when that becomes an official way to load modules ES6 modules may already be native, thatās my guess).
In general, I think we can continue using ES6 for certain things, just not for class definitions. Some things compile to really simple ES5 with no function wrappers. Weāve gotta experiment/research to find out what exactly.
No. I meant the class wrappers in this case.
Yeah, I agree with supporting as much as possible and doing the rest as it goes native. Could even have both versions, to show the benchmark for why we are still there. That way we know when to make the change also.
I personally would first write my code in ES6, create my benchmark tests, then convert to the performance enhanced equivalent.
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.
@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. 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
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. 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
Edit: sorry this is an older post that I moved accidentally and now is here out of order since I moved it back
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 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:
- It means youāll be able to write code that will work transpiled but break in native.
- 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 eval
ing 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() {},
})