TypeScript's useDefineForClassFields is Slow
Last updated
When benchmarking Gooey, I noticed a surprising bottleneck: the amount of time it took to construct class instances.
tl;dr: If you write TypeScript and have an application that constructs many classes, consider using the
declare
keyword before member type declarations, or setuseDefineForClassFields
tofalse
.
Gooey uses a "Field" class to represent every piece of mutable data. This means
it constructs a very large number of these classes. When benchmarking, I
noticed that a surprising bottleneck was a call to a mysterious
__defNormalProp
function, which was taking a surprising amount of time.
This function was injected due to my use of esbuild which was picking up the
configuration value "useDefineForClassFields": true
in my tsconfig.json
.
This is documented in esbuild issue 185. Essentially, when you write something like this:
class MyClass {
value: number;
constructor(value: number) {
this.value = value;
}
}
Both TypeScript and esbuild output something like this:
class MyClass {
constructor(value) {
Object.defineProperty(this, "value", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.value = value;
}
}
As this jsbench.me benchmark shows, this is surprisingly slow! Like 90% slower!
There are two ways of getting around this: On a per-project basis, you can set useDefineForClassFields
to false
. On
a per-field basis you can use the declare
keyword on the prop in the class:
class MyClass {
declare value: number;
constructor(value: number) {
this.value = value;
}
}
Both of these approaches will avoid that call to Object.defineProperty
.
Note: if you take this approach and your code does not assign to the member in the constructor, that member will not appear as a member of the object.
More about what this does and why here: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier