`static` in Javascript: Language quirks everywhere!
Couple of weeks back at work I ran across this scenario —
I had to pass in a variable that gets initialised a few milliseconds post first render and then pass this variable down to roughly 12~15 nodes down the DOM tree.
I immediately ruled out prop drilling my way out of this problem — who in their right minds would mutate 15 different interface files, 15 more component files and potentially all of their test files too? Just for passing ONE variable.
Another option was to append this to our ever growing state — the exact reason why we are moving away from it. People run across similar scenarios all the time and use the global state as crutches to mitigate this problem, which just goes ahead and pollutes it.
I was then asked to check if static could somehow help mitigate this problem — which it did!
I start off with this
//App.tsx
import { OuterComponent } from "./OuterComponent";
import "./styles.css";
export default function App() {
const val = "some_val";
return (
<div className="App">
<OuterComponent />
</div>
);
}
//Outcomponent.tsx
import { InnerComponent } from "./InnerComponent";
export const OuterComponent = () => {
return (
<div>
<span>OuterComponent: I need to pass the value</span>
<InnerComponent />
</div>
);
};
//InnerComponent.tsx
export const InnerComponent = () => {
return <div>`InnerComponent: I need to render the value`</div>;
};
We get a simple output on our screen
Our goal now is to pass this val
present inside App.tsx
to our InnerComponent.tsx
We start off by writing a static class like so:
export class HoldClass {
private static val = "no_val";
static initalize(value: string) {
this.val = value;
}
static getVal() {
return this.val;
}
}
I have done 3 things here:
- Define a static variable
val
and initialise it - Define an initialization function for our val
- Define a getter
getVal
for our value
Normally, when we think of Classes, we think of initialising them and then using properties present in them throughout our application. However, static class variables behave a bit differently to regular variables!
A better way to think about them is that they belong to the class itself, rather than an instance of a class. This property allows us to use them in some very specific scenarios.
Coming back to our example, there are just two modifications now required
We first need to initialise our static variable with the value in App.tsx
import { HoldClass } from "./HoldClass";
import { OuterComponent } from "./OuterComponent";
import "./styles.css";
export default function App() {
const val = "some_val";
HoldClass.initalize(val)
return (
<div className="App">
<OuterComponent />
</div>
);
}
import { HoldClass } from "./HoldClass";
export const InnerComponent = () => {
return <div>`InnerComponent: I need to render the value {HoldClass.getVal()}`</div>;
};
And then we simply access it inside the InnerComponent!
And that’s it, the value now renders perfectly fine on our screen
There are some intersting gotcha’s and language specific perks though…
Static variables in JS —and all the special treatment they recieve
Static variables are allocated in the global object space, not in the heap where most objects are stored. This means they persist for the lifetime of the application or until the class is unloaded.
Secondly, Static variables are not subject to garbage collection as long as the class exists. This can be both an advantage (no GC overhead) and a potential memory leak if not managed properly.
This raises the question of why not just rely on statics all the time rather than constants?
We can do a quick experiment to find out why sticking to constants might just be better for most scenarios:
Access time
I ran a simple script to benchmark access performance with both static and constants —
class WithStatic {
static value = 42;
}
const WithConst = {
value: 42,
};
function testStatic() {
return WithStatic.value;
}
function testConst() {
return WithConst.value;
}
// Benchmark
console.time("Static");
for (let i = 0; i < 1000; i++) {
testStatic();
}
console.timeEnd("Static");
console.time("Const");
for (let i = 0; i < 1000; i++) {
testConst();
}
console.timeEnd("Const");
We can clearly see in some renders static is far behind than constants, wheras in some its marginally behind. However, the one consistent thing here is — it is generally slower to access than constants.
But wait — there’s more!
Tree-shaking
Constants are easier for bundlers to tree-shake.
// static.js
export class WithStatic {
static value = 42;
}
// const.js
export const WithConst = {
value: 42
};
import { WithStatic } from './static';
import { WithConst } from './const';
console.log(WithConst.value);
Using a bundler like Webpack with tree-shaking enabled, you’ll find that the WithStatic class is included in the bundle even though it’s not used, while WithConst can be tree-shaken out if unused. (although this is subject to change as webpack evolves and might be handled differently in different bundlers)
TL;DR:
- Choose between static variables and constants based on your design needs and code clarity, not on micro-optimizations.
- Use constants (
const
) for values that should not change. - Use static variables when you need shared, mutable state across all instances of a class.