The answer comes from Zhihu:
Click the jump
1. Native DOM manipulation vs. encapsulated manipulation through the framework.
It’s a performance vs. maintainability trade-off. The point of a framework is to mask the underlying DOM manipulation for you and make your code more maintainable by allowing you to describe your purpose in a more declarative way. No framework can be faster than purely manual DOM optimization, because the framework’s DOM manipulation layer needs to handle any operations that the upper API might produce, and its implementation must be universal. I can write manual optimizations faster than any framework for any benchmark, but what’s the point? When building a real application, do you manually optimize everything? This is obviously not possible for maintainability reasons. The framework guarantees you that I can still give you decent performance without having to manually optimize.
2. The React Virtual DOM misunderstanding.
React never says that React is faster than the native DOM operation. React’s basic mind-set is to re-render the entire application every time it changes. Without the Virtual DOM, the simple idea is to reset innerHTML directly. What a lot of people don’t realize is that resetting innerHTML is actually a reasonable operation in a large list where everything changes… The real problem is that in an “all re-render” mindset, even if only one row of data changes, it needs to reset the entire innerHTML, which is obviously a lot of waste.
We can compare the redraw performance cost of innerHTML vs. Virtual DOM:
- InnerHTML: render HTML string O(template size) + create all DOM elements O(DOM size)
- Render Virtual DOM + diff O(Template size) + necessary DOM update O(DOM change)
3. MVVM vs. Virtual DOM
In contrast, other MVVM frameworks such as Angular, Knockout, Vue, and Avalon all use data binding: Using a Directive/Binding object, you can observe changes in data and retain references to the actual DOM elements, and perform operations when data changes. MVVM change checks are at the data level, while React checks are at the DOM structure level. MVVM performance also varies depending on how change detection is implemented: Angular’s dirty check makes any change have a fixed O(Watcher count) cost; Knockout/Vue/Avalon all use dependency collection and are O(change) at both the JS and DOM levels:
- Scope Digest O(Watcher Count) + Necessary DOM update O(DOM change)
- Dependency Collection: Re-collect dependencies O(Data change) + necessary DOM updates O(DOM change)
When MVVM renders a list, since each row has its own data scope, there is usually a corresponding ViewModel instance for each row, or a slightly lighter “scope” object that takes advantage of archetypal inheritance, but at a cost. As a result, MVVM list rendering initialization is almost certainly slower than React, because creating ViewModel/Scope instances is much more expensive than Virtual DOM. A common problem with all MVVM implementations here is how to effectively reuse already created ViewModel instances and DOM elements when the data source for list rendering changes, especially when the data is a brand new object. Without any reuse optimizations, since the data is “new,” MVVM actually needs to destroy all previous instances, recreate all instances, and finally render again! This is why the Angular/Knockout implementations linked in the title are relatively slow. In contrast, React’s change check is based on the DOM structure level. Even if the data is new, as long as the final rendering result is unchanged, there is no need to do useless work.
Angular and Vue both provide optimization mechanisms for list redrawing, which is how the “hint” framework effectively reuses instances and DOM elements. For example, the same object in the database can become a different object in two front-end API calls, but they still have the same UID. You can prompt Track by uid to let Angular know that the two objects are the same data. The original instance and DOM element of this data can be reused, and only the changed part needs to be updated. Alternatively, you can simply track by $index to “in-place reuse” : reuse by location in the array. In the example given in this section, if angular implemented track by $index, subsequent redraws would not be much slower than React. Even in dbMonster tests, Angular and Vue performed faster with Track by $index than React: dbmon
By the way, React renders lists with special prop keys, which are essentially the same thing as track-by.
4. Performance comparisons also depend on the occasion
When comparing performance, distinguish between initial rendering, small data updates, and large data updates. Virtual DOM, dirty check MVVM, data collection MVVM have different performance and different optimization requirements in different situations. The Virtual DOM also needs specific optimizations, such as shouldComponentUpdate or IMmutable data, to improve the performance of small data updates.
- Initial render: Virtual DOM > Dirty Check >= Dependency collection
- Small amount of data updates: Dependency collection >> Virtual DOM + Optimization > Dirty Check (Unable to Optimize) > Virtual DOM No optimization
- Massive data updates: Dirty check + optimization >= Dependent collection + Optimization > Virtual DOM (unavailable/no optimization required) >> MVVM No optimization
5. To summarize
These comparisons are more of a reference for framework developers. The mainstream framework + reasonable optimization is sufficient to meet the performance requirements of most applications. For special cases with extreme performance requirements, some maintainability should be sacrificed and manual optimization should be adopted. For example, Atom editor uses its own tile-based rendering instead of React for file rendering. For example, the virtual roll of DOM-pooling on the mobile end does not need to consider the order change, and you can bypass the built-in implementation of the framework to create one.