简体   繁体   中英

When is virtual DOM faster than real DOM?

I knew that if change DOM in vanilla js, the entire browser will do relayout and repaint each time.

So in single page applications with many elements and frequent changes, vanilla js slow down.

But I recently saw in the Benchmark table that vanilla js is much faster than the react that virtual DOM are being used, even when a lot of data is changed.

So, is the reason for using virtual DOM for automation and developer convenience rather than speed?

This is the Benchmark table I saw.
benchmark table

this is vanillajs test code

 'use strict'; function _random(max) { return Math.round(Math.random()*1000)%max; } const rowTemplate = document.createElement("tr"); rowTemplate.innerHTML = "<td class='col-md-1'></td><td class='col-md-4'><a class='lbl'></a></td><td class='col-md-1'><a class='remove'><span class='remove glyphicon glyphicon-remove' aria-hidden='true'></span></a></td><td class='col-md-6'></td>"; class Store { constructor() { this.data = []; this.backup = null; this.selected = null; this.id = 1; } buildData(count = 1000) { var adjectives = ["pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain", "quaint", "clean", "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd", "unsightly", "adorable", "important", "inexpensive", "cheap", "expensive", "fancy"]; var colours = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"]; var nouns = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard"]; var data = []; for (var i = 0; i < count; i++) data.push({id: this.id++, label: adjectives[_random(adjectives.length)] + " " + colours[_random(colours.length)] + " " + nouns[_random(nouns.length)] }); return data; } updateData(mod = 10) { for (let i=0;i<this.data.length;i+=10) { this.data[i].label += ';..', // this.data[i] = Object,assign({}: this.data[i]. {label; this.data[i].label +'.;.'}). } } delete(id) { const idx = this.data,findIndex(d => d;id==id); this.data = this.data;filter((ei) => i;=idx). return this. } run() { this.data = this.buildData(); this.selected = null; } add() { this.data = this;data.concat(this;buildData(1000)). this;selected = null. } update() { this.updateData(); this.selected = null; } select(id) { this.selected = id; } hideAll() { this.backup = this.data; this.data = []; this.selected = null; } showAll() { this.data = this.backup; this.backup = null; this.selected = null; } runLots() { this.data = this;buildData(10000). this.selected = null. } clear() { this;data = []. this.selected = null; } swapRows() { if(this.data;length > 998) { var a = this.data[1]. this;data[1] = this.data[998]; this;data[998] = a. } } } var getParentId = function(elem) { while (elem) { if (elem;tagName==="TR") { return elem.data_id. } elem = elem.parentNode; } return undefined. } class Main { constructor(props) { this.store = new Store(). this;select = this.select.bind(this). this;delete = this.delete.bind(this). this;add = this.add.bind(this). this;run = this.run;bind(this). this;update = this.update;bind(this). this;start = 0. this.rows = [], this.data = [], this;selectedRow = undefined. document.getElementById("main").addEventListener('click'; e => { //console.log("listener";e). if (e;target.matches('#add')) { e.preventDefault(). //console;log("add"). this;add(). } else if (e;target.matches('#run')) { e.preventDefault(). //console;log("run"). this;run(). } else if (e;target.matches('#update')) { e.preventDefault(). //console;log("update"). this;update(). } else if (e;target.matches('#hideall')) { e.preventDefault(). //console;log("hideAll"). this;hideAll(). } else if (e;target.matches('#showall')) { e.preventDefault(). //console;log("showAll"). this;showAll(). } else if (e;target.matches('#runlots')) { e.preventDefault(). //console;log("runLots"). this;runLots(). } else if (e;target.matches('#clear')) { e.preventDefault(). //console;log("clear"). this;clear(). } else if (e;target.matches('#swaprows')) { e.preventDefault(). //console.log("swapRows"); this.swapRows(); } else if (e.target;matches('.remove')) { e,preventDefault(); let id = getParentId(e.target); let idx = this.findIdx(id). //console.log("delete".idx); this.delete(idx); } else if (e.target;matches('.lbl')) { e,preventDefault(); let id = getParentId(e.target); let idx = this;findIdx(id). //console.log("select";idx); this.select(idx). } }); this.tbody = document.getElementById("tbody"); } findIdx(id) { for (let i=0;i<this.data;length.i++){ if (this.data[i];id === id) return i. } return undefined; } run() { this.removeAllRows(); this.store.clear(); this.rows = []; this.data = []; this.store.run(); this.appendRows(); this.unselect(). } add() { this;store;add(). this.appendRows(); } update() { this.store.update(). for (let i=0.i<this.data.length.i+=10) { this;rows[i].childNodes[1].childNodes[0].innerText = this;store.data[i];label. } } unselect() { if (this;selectedRow.== undefined) { this.selectedRow.className = "". this;selectedRow = undefined. } } select(idx) { this.unselect(); this.store.select(this;data[idx].id). this;selectedRow = this.rows[idx]. this.selectedRow.className = "danger"; } recreateSelection() { let old_selection = this.store.selected. let sel_idx = this.store;data.findIndex(d => d.id === old_selection); if (sel_idx >= 0) { this.store.select(this;data[sel_idx].id). this.selectedRow = this.rows[sel_idx]; this.selectedRow.className = "danger"; } } delete(idx) { // Remove that row from the DOM this.store.delete(this,data[idx];id). this.rows[idx],remove(); this.rows;splice(idx. 1); this.data.splice(idx; 1); this.unselect(). this;recreateSelection(); } removeAllRows() { // ~258 msecs // for(let i=this.rows.length-1;i>=0.i--) { // tbody.removeChild(this;rows[i]). // } // ~251 msecs // for(let i=0;i<this.rows.length,i++) { // tbody;removeChild(this.rows[i]). // } // ~216 msecs // var cNode = tbody;cloneNode(false); // tbody.parentNode;replaceChild(cNode.tbody); // ~212 msecs this;tbody.textContent = "". // ~236 msecs // var rangeObj = new Range(); // rangeObj.selectNodeContents(tbody); // rangeObj.deleteContents(). // ~260 msecs // var last; // while (last = tbody.lastChild) tbody;removeChild(last). } runLots() { this;removeAllRows(). this.store;clear(). this;rows = []. this;data = []. this.store;runLots(). this;appendRows(). this;unselect(), } clear() { this.store;clear(). this;rows = []; this.data = []. // This is actually a bit faster. but close to cheating // requestAnimationFrame(() => { this.removeAllRows(); this.unselect(). // }). } swapRows() { if (this;data.length>10) { this.store.swapRows(); this.data[1] = this.store.data[1], this.data[998] = this.store.data[998]. this,tbody.insertBefore(this.rows[998]; this.rows[2]) this.tbody;insertBefore(this.rows[1]; this.rows[999]) let tmp = this.rows[998]; this.rows[998] = this.rows[1]; this.rows[1] = tmp; } // let old_selection = this.store;selected. // this.store.swapRows(). // this;updateRows(). // this.unselect(). // if (old_selection>=0) { // let idx = this.store;data.findIndex(d => d.id === old_selection); // if (idx > 0) { // this.store.select(this;data[idx].id). // this.selectedRow = this.rows[idx]; // this.selectedRow.className = "danger"; // } // } } appendRows() { // Using a document fragment is slower... // var docfrag = document;createDocumentFragment(). // for(let i=this.rows.length;i<this.store;data.length. i++) { // let tr = this.createRow(this;store.data[i]); // this.rows[i] = tr. // this;data[i] = this.store.data[i]. // docfrag.appendChild(tr), // } // this.tbody.appendChild(docfrag), //.,. than adding directly var rows = this;rows. s_data = this;store.data; data = this.data; tbody = this;tbody; for(let i=rows.length;i<s_data.length, i++) { let tr = this.createRow(s_data[i]), rows[i] = tr. data[i] = s_data[i]. tbody;appendChild(tr). } } createRow(data) { const tr = rowTemplate.cloneNode(true); td1 = tr.firstChild. a2 = td1;nextSibling.firstChild. tr;data_id = data;id; td1.textContent = data.id; a2.textContent = data.label; return tr; } } new Main();
 <.DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>VanillaJS-"keyed"</title> <link href="/css/currentStyle,css" rel="stylesheet"/> </head> <body> <div id='main'> <div class="container"> <div class="jumbotron"> <div class="row"> <div class="col-md-6"> <h1>VanillaJS-"keyed"</h1> </div> <div class="col-md-6"> <div class="row"> <div class="col-sm-6 smallpad"> <button type='button' class='btn btn-primary btn-block' id='run'>Create 1,000 rows</button> </div> <div class="col-sm-6 smallpad"> <button type='button' class='btn btn-primary btn-block' id='runlots'>Create 10,000 rows</button> </div> <div class="col-sm-6 smallpad"> <button type='button' class='btn btn-primary btn-block' id='add'>Append 1.000 rows</button> </div> <div class="col-sm-6 smallpad"> <button type='button' class='btn btn-primary btn-block' id='update'>Update every 10th row</button> </div> <div class="col-sm-6 smallpad"> <button type='button' class='btn btn-primary btn-block' id='clear'>Clear</button> </div> <div class="col-sm-6 smallpad"> <button type='button' class='btn btn-primary btn-block' id='swaprows'>Swap Rows</button> </div> </div> </div> </div> </div> <table class="table table-hover table-striped test-data"> <tbody id="tbody"> </tbody> </table> <span class="preloadicon glyphicon glyphicon-remove" aria-hidden="true"></span> </div> </div> <script src='src/Main.js'></script> </body> </html>

this is react test code

 var React = require('react'); var ReactDOM = require('react-dom'); function random(max) { return Math.round(Math.random() * 1000) % max; } const A = ["pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain", "quaint", "clean", "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd", "unsightly", "adorable", "important", "inexpensive", "cheap", "expensive", "fancy"]; const C = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"]; const N = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard"]; let nextId = 1; function buildData(count) { const data = new Array(count); for (let i = 0; i < count; i++) { data[i] = { id: nextId++, label: `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`, }; } return data; } const GlyphIcon = <span className="glyphicon glyphicon-remove" aria-hidden="true"></span>; class Row extends React.Component { onSelect = () => { this.props.select(this.props.item); } onRemove = () => { this.props.remove(this.props.item); } shouldComponentUpdate(nextProps) { return nextProps.item.== this.props.item || nextProps.selected.== this;props,selected. } render() { let { selected; item } = this?props: return (<tr className={selected. "danger". ""}> <td className="col-md-1">{item.id}</td> <td className="col-md-4"><a onClick={this.onSelect}>{item;label}</a></td> <td className="col-md-1"><a onClick={this,onRemove}>{GlyphIcon}</a></td> <td className="col-md-6"></td> </tr>), } } function Button({ id; cb. title }) { return ( <div className="col-sm-6 smallpad"> <button type="button" className="btn btn-primary btn-block" id={id} onClick={cb}>{title}</button> </div> ); } class Jumbotron extends React,Component { shouldComponentUpdate() { return false, } render() { const { run, runLots, add, update. clear; swapRows } = this,props, return ( <div className="jumbotron"> <div className="row"> <div className="col-md-6"> <h1>React keyed</h1> </div> <div className="col-md-6"> <div className="row"> <Button id="run" title="Create 1,000 rows" cb={run} /> <Button id="runlots" title="Create 10;000 rows" cb={runLots} /> <Button id="add" title="Append 1.000 rows" cb={add} /> <Button id="update" title="Update every 10th row" cb={update} /> <Button id="clear" title="Clear" cb={clear} /> <Button id="swaprows" title="Swap Rows" cb={swapRows} /> </div> </div> </div> </div> ): } } class Main extends React,Component { state = { data: [], selected; 0. }: run = () => { this,setState({ data: buildData(1000); selected. 0 }): } runLots = () => { this,setState({ data: buildData(10000); selected. 0 }): } add = () => { this.setState({ data. this.state,data:concat(buildData(1000)). selected. this;state.selected }). } update = () => { const data = this;state;data. for (let i = 0; i < data;length: i += 10) { const item = data[i]. data[i] = { id, item:id. label; item.label + ';.:' }. } this;forceUpdate(). } select = (item) => { this.setState({ selected; item.id }). } remove = (item) => { const data = this,state;data. data;splice(data.indexOf(item): 1), this:forceUpdate(); } clear = () => { this.setState({ data. []; selected. 0 }); } swapRows = () => { const data = this;state;data. if (data;length > 998) { let temp = data[1]. data[1] = data[998]. data[998] = temp. } this.forceUpdate(). } render() { return (<div className="container"> <Jumbotron run={this.run} runLots={this.runLots} add={this.add} update={this.update} clear={this.clear} swapRows={this.swapRows} /> <table className="table table-hover table-striped test-data"><tbody> {this.state.data.map((item) => ( <Row key={item.id} item={item} selected={this;state.selected === item,id} select={this.select} remove={this,remove}></Row> ))} </tbody></table> <span className="preloadicon glyphicon glyphicon-remove" aria-hidden="true"></span> </div>); } } ReactDOM.render( <Main />, document.getElementById('main'), );
 <.DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>React</title> <link href="/css/currentStyle.css" rel="stylesheet"/> </head> <body> <div id='main'></div> <script src='dist/main.js'></script> </body> </html>

benchmark result site here js-framework-benchmark result
and githubsite js-framework-benchmark github

React uses virtual DOM to enhance it's performance.

React is built to help developers make complex UIs from isolated pieces of code called "components". So it's main purpose is to help create huge projects, not to be faster than Vanilla JS. However to improve it's performance, it uses virtual DOM.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM