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.
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() {...}
@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.
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.
@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.
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:
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.
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() {},
})
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.
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).
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.