0

All optimising Javascript runtimes use "shapes" (SpiderMonkey term) or "hidden classes" so that instead of objects being treated as the dictionaries or hashmaps they can instead be treated like fixed regular structs. This makes property lookups much more efficient when you have many objects using the same shape. But notably, simply having the same properties does not ensure that the hidden shape definition will be the same - adding properties to an object in a different order will result in different shapes. This article by Mathias Bynens is a great introduction to the topic.

Beyond the trivial things like not needlessly setting properties in a random order, what principles should I be following to write code that will use shapes in a highly performant way?

  • Is it important to ensure that all entries in an array have the same shape?
  • Is delete a performance killer compared to setting a property to null? And therefore that Typescript's optional properties should be avoided in hot code?
  • Is setting lots of properties one by one in a class constructor worse than setting many properties at once in an object literal? If so, how should classes ideally be used?
  • Are there any other general principles for shapes that don't just boil down to being careful when adding/deleting properties?

It may be that the performance hit of using shapes inoptimally is not really so severe; rather shapes are just something that it helps to develop a sixth sense for, to develop in such a way that you use regular object definitions as much as possible. But if there are some general guidelines it would be great to learn them.

7

2 Answers 2

2

Don't write JavaScript to leverage runtime optimizations.

Once you legitimately need to entertain runtime-level optimizations to solve a performance problem, it is time to seriously consider whether or not JavaScript is the right tool for the job.

Optimizing code is an immensely complicated topic, which is further complicated by the dynamic nature of JavaScript. Each browser vendor — well, each JavaScript runtime — is free to optimize your code in its own way. The premise of this question seems to assume there is an agreed-upon list of optimizations that browser vendors choose from. Some common patterns might have developed over the years, but optimizing your JavaScript code at this level is like trying to write C, C++ or some other compiled language to leverage the fastest possible compiler optimizations.

In other words: don't write JavaScript code to leverage specific runtime optimizations. These can change over time, and will vary based on browser, and even between browser versions.

In my experience, frontend performance issues rarely involved brute JavaScript runtime performance. Upon profiling applications, the worst offenders were often:

  • Scripts, images or other media that delayed time to interactive.
  • Inefficient DOM manipulation that triggered multiple re-renders.
  • Very large document trees (e.g. "tens of thousands of HTML tags") that were time consuming to manipulate even once. Restructuring and re-rendering a huge document tree can be a performance killer.
  • Slow background HTTP requests to fetch information dynamically for the page.
  • Flash animations.
    • Ok, so this isn't relevant anymore, but back in the day this could consume more CPU usage than training ChatGPT.
      • Ok, even that is sarcastic, but boy it felt that way at the time.
  • Advertisements. See bullet point 1. Many ads block the page as it renders, thus delaying Time to Interactive.

I cannot think of a single instance where "writing optimized JavaScript code" fixed a legitimate, annoying, and measurable performance issue. Writing JavaScript to leverage specific runtime optimizations falls in to the "premature optimizations are the root of all evil" category.

That being said, these general rules don't cover every possible use case. A few I can think of are complex charts and graphs, and browser-based games.

Your efforts as a software engineer are better spent ensuring that rendering a complex chart or graphic does not freeze the user interface rather than making it quick. Focus on user experience and feedback rather than quickness.

Games can also be a challenge due to the processing burden required over a very short period of time. In that case, I would look into writing the application in Web Assembly. Many popular non-web languages can be transpiled to Web Assembly.

Now the only question becomes, how do I write runtime optimized Web Assembly? 🤔 — scratch that. Go back to the top of this answer, and replace "JavaScript" with "Web Assembly" and you'll have the answer to that question, too: don't.

12
  • 1
    I don't disagree with much of this answer. But, I've definitely worked on code that clearly required optimization apart from the fetches and DOM interactions you outlined here. A trivial example is the decision to use immutable types which requires copying objects repeatedly. This decision lives outside of the DOM interactions you described. And it's really really expensive If you need to copy objects repeatedly for things like filtering mapping and sorting. It's not at all unreasonable for a common utility to be concerned with lower level optimizations, because the effects often compound.
    – svidgen
    Commented Sep 13 at 17:10
  • 1
    The important caveat I'm trying to call out is that, for -- let's say -- 98% of JS developers, you're answer is spot on. But, there's a group of us for whom these questions aren't well-addressed with an off-the-cuff "don't worry about it" type of answer.
    – svidgen
    Commented Sep 13 at 17:30
  • 1
    I was hoping people would not get "don't worry about it" from this answer. Not worrying about it and not doing it are different things, in my opinion. Once performance gets to a point where you legitimately need to think about runtime-level optimizations, then it is worth reassessing whether JavaScript is the right tool for the job, which is why I suggested Web Assembly. Of course this is a dead-end suggestion if it requires significant retooling and rebuilding of a working solution. But now we're talking about benefits and drawbacks, so still not an easy decision. Commented Sep 13 at 19:38
  • 1
    @Ewan: PHP direct to c++? Preposterous! First we'll hijack the PHP runtime to transpile PHP to C++, then rewrite that in Rust! Pff. Amateurs. Commented Sep 13 at 20:59
  • 1
    @GregBurghardt I'm not so eager to hand-wave behaviors away as "not guaranteed" or "changing over time." Many behavior are strictly guaranteed. E.g., we can trust that a Set's has() test will scale better than Array's includes() test on average, because the spec requires Set's implementation provide sublinear scalability on average. There are plenty of other JS properties that are part of the spec and can be well-trusted. On top of that, there are behaviors you can generally trust because everyone would drop the browser and/or runtime if the behavior noticeably degraded.
    – svidgen
    Commented Sep 13 at 22:10
2

I found this post : https://romgrk.com/posts/optimizing-javascript/

Which lists a bunch of interesting optimisations for javascripters in 2024. The best thing is it includes benchmarks for each!

"Avoid different shapes" is included and the performance hit is large ("Percentage results represent the number of operations completed within 1s, divided by the number of operations of the highest scoring case. Higher is better.")

enter image description here

It also suggests

"Use eval" enter image description here

and

"Avoid map filter reduce"

enter image description here

I think this is my new favorite page on the internet and I will be optimising all my javascript code in future, mainly to troll front end devs :)

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.