I have only looked at Angular2 and t7.
The two frameworks make different assumptions on how to interpret the change in data and as a result mutate the DOM in very different way, which then results in a material performance difference.
The mutation of the data happens in generateData() method. The generateData returns a new array with new items in it each time it runs. This has important implication. Because the rows in the returned array have different identities, there are two ways of interpreting the data:
- Ignore the identity and assume that first item in array is same as first item in the old array. This is what t7 does. Implication is that we just have to update the existing DOM structure with the new values and we are done.
- Look at old/new array and compute how the identities changed. This is what Angular 1 and 2 do. In this case the change is interpreted as all items left the array and new set of items were inserted. Angular will try to honor the removal/inserts and so it will remove all DOM rows for the items which were removed and then insert a new set of DOM rows for the new items back in. Angular 2 can do this relatively efficiently, because we can cache and reuse the rows.
Both strategies, look reasonable at first, until you try to repeat over user input elements, or try do do animation, then strategy 1 breaks.
- Animation: reusing the row will not animate old row away and new one in. At best it could animate new rows additions or extra rows removal. The result is that inserting a new item in the front of the array will result in data jumping to next row (identities are not checked) and the last row is animated in. This does not reflect what actually happens in the model.
- User Input: If the user is editing a form which has repeated rows, inserting a new row in the front will clobber all values including the user cursor and or selection.
Because of the above corner cases which are hard to debug (insertions at end works, but insertion in the middle does not, but only if user is typing and or animations are enabled), Angular uses the second strategy. In most real life applications this is not an issue, since it is very rare that the model is constantly changing the identity. In real life cases model either does not change or only changes values. If the model only changed values but not the object identity, then Angular would not delete/insert rows and the performance would be identical.
Which brings are to the last use case of track-by. There are cases where you want Angular to treat two objects with different identities as equivalent. For example calling the backend and getting a refreshed dataset from server would appear as new objects which would then animate the existing data away and animate new data in. In such a case you can specify a track by function, which allows Angular to recognize that two objects should be treated as equivalent and reuse the corresponding rows. This is why Angular 1 with track by performs better then Angular 2 without track by, because the track by causes Angular 1 to interpret the data in the #1 way. BTW, track-by is coming to Angular 2 as well, we just have not gotten to it.
The summary is that these perf tests do not compare apples to apples. Because generateData() method creates new data set each time, it is hard to say which strategy individual frameworks have chosen to implement when doing their updates. My suggestion would be to update generateData() method so that it updates the data in a way which keeps the identities same. This is a much more realistic case, in which case all of the frameworks will interpret the change in a same way.