This benchmark is significantly better than vdom-benchmark, because its result isn’t just a javascript execution time, it also measures recalc style, reflow, paint, composition etc. But it is also a synthetic test with all its downsides.

Test cases that are used in this benchmark probably were chosen because it is easy to implement them efficiently in Vanilla JS, so Vanilla JS implementation can be used as a baseline. But I guess that the author of this benchmark was surprised when we’ve started to add Virtual DOM implementations that was optimized to win benchmarks.

Vanilla JS implementation couldn’t compete with our libraries in many test cases and it was completely rewritten and now it performs DOM operations in the same way as Virtual DOM implementations.

For example, this is how it will remove a single row:

for(let i=this.rows.length-2; i>=idx;i--) {

let tr = this.rows[i];

let data = this.store.data[i+1];

tr.data_id = data.id;

tr.childNodes[0].innerText = data.id;

tr.childNodes[1].childNodes[0].innerText = data.label;

this.data[i] = this.store.data[i];

}

this.store.delete(this.data[idx].id);

this.data.splice(idx, 1);

this.rows.pop().remove();

If you are Vanilla JS master and think that you can always perform DOM manipulations way more efficiently than using some kind of abstraction like Virtual DOM, try to understand why this is faster than simple removal of a single row.

I don’t want to write about libraries that have extremely bad results in this benchmark. I’ll focus on the leaders and how they get their results.

As we can see, with this benchmark it is way much harder to improve rendering performance with recycling, because it doesn’t perform several iterations in one session.

All libraries are pretty fast in this test case, but there are still some key differences between implementations. Some implementations are using an old optimization technique with event delegation and some don’t.

The one thing that I’d like to mention is that React, Bobril and vidom implement event delegation implicitly and hide all implementation details from their users. Other libraries like kivi and Inferno are using event delegation explicitly in their benchmark implementations.

I’ve implemented it explicitly with kivi just to get better results in this benchmark, and I think that this is a kind of “cheating”. Explicit event delegation in React-like libraries is an anti-pattern.

But it also wouldn’t be fair not to mention that implicit delegation also has its problems, this issue perfectly describes why it can cause scroll jank on mobile browsers.

This is the most interesting piece in this benchmark.

Leon Sorokin (domvm author) found out that if we disable track by key algorithm and will use naive algorithm to rearrange children lists we can get amazing results in this test cases, because it won’t trigger long-running recalc style. In other words, just render children list without key property in React, disable track-by in angular or vue, or do some magic to get rid of tracking by object identity.

The reason for this strange behavior is nth-of-type(odd) selector that is used for rows in this benchmark. Implementations that properly rearrange, replace and remove nodes will be way much slower than implementations that update existing nodes, and always remove the latest rows.

This “optimization” technique does the trick because rows doesn’t have any complicated structure and updates are trivial.

Also, this benchmark doesn’t have any other test cases with moving nodes, etc. Swapping rows is the simplest transformation:

A: [a b c d e f]

B: [f b c d e a]

Here sync algorithm need to perform two updates a -> f and f -> a. But imagine a much more common situation with one move:

A: [a b c d e f]

B: [b c d e f a]

Now it will need to perform six updates, because we removed all keys and syncing algorithm won’t be able to move node with one insertBefore operation.

And even if this benchmark doesn’t have any internal state in rows, and it should be ok to remove all keys, I think that this is completely wrong, and the only library that has the correct behavior in such situations is React. It will immediately print a warning that key properties are missing. This is one of the most common errors when you working with web UI libraries.

Here is what will happen if you forget to add keys and then someone decides to add a CSS transition in one of the components that are used in this rows:

When item is removed from the list without keys, the next item will use css transition state from the removed row, instead of starting a new transition on the next row. That is because when you click on a row to remove, you are actually removing the latest row and all other rows are modified, so it looks like you removed a row that has been clicked, and now all nodes have incorrect internal state.