Description
I was trying to find places to optimise a large project, and realised that the longest running function was actually the __extends call, which prompted me to look at the current code.
Current code
Two things immediately jumped out at me with the current code:
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
Problems
Potentially unused code (pre ES5)
The first problem was that the __()
function was being created, when it might not be used (though chances of that are probably very small).
The Solution would be to move the check into an if/else. Related to this is that null
doesn't have any properties, so the property copying should be in the "not null" section.
var __extends = (this && this.__extends) || function (d, b) {
if (b===null) {
d.prototype = Object.create(b);
} else {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
var __=function() { this.constructor = d; };
__.prototype = b.prototype;
d.prototype = new __();
}
};
Inefficient iteration (for ES5+)
The second problem is purely performance related with the property copying. for(var p in b)
will iterate over the entire prototype tree, but if(b.hasOwnProperty(p))
will then skip any that aren't directly on the object/class itself. (see Enumerability and ownership of properties)
The solution would be to use Object.keys(b)
and iterating an array, which will work only on the object/class itself (and not it's prototype tree), it also automatically skips the same keys we'd be skipping anyway - so taking most of what we're wanting to the browser / native code instead of JS.
Unlike for...in
, Object.keys fails when called on null
, undefined
or any other non-Object, so this requires some extra code to help prevent that happening.
From here we see that Object.keys()
is an ES5+ function.
Suggested code:
var __extends = (this && this.__extends) || function (d, b) {
if (b instanceof Object) {
Object.keys(b).forEach(function(p){
d[p] = b[p];
});
var __=function() { this.constructor = d; };
__.prototype = b.prototype;
d.prototype = new __();
} else {
d.prototype = Object.create(null);
}
};
Results
It's very hard to get reliable timings for the code on Chrome Profiling, but on average this looks like roughly a 10% speedup at startup for my project (38 classes in a nested tree up to six deep). There is no major benefit to this, though it does make the code slightly "safer".
The ES5+ code could potentially cause different behaviour if a class tries to inherit from a non-class (such as a number or string), though I'm not sure that is possible to begin with..
Language Feature Checklist
- Syntactic: No changes as this is a compiler change.
- Semantic: No changes as the final code does the same thing when used as intended (see Results above).
- Emit: Output code will change depending on ES version needed, both versions are slightly larger than the original.
- Compatibility: Two versions, one for pre-ES5, most efficient for ES5+
- Other: There is already an option to prevent __extends being output (which only happens if needed anyway). This could potentially hit older build scripts that manually search & replace the old code.