繁体   English   中英

什么时候虚拟 DOM 比真实 DOM 快?

[英]When is virtual DOM faster than real DOM?

我知道如果在 vanilla js 中更改 DOM,整个浏览器每次都会重新布局和重新绘制。

所以在元素多、变化频繁的单页应用中,vanilla js 会变慢。

但我最近在 Benchmark 表中看到,vanilla js 比使用虚拟 DOM 的反应要快得多,即使在更改大量数据时也是如此。

那么,使用虚拟 DOM 的原因是为了自动化和开发人员的便利,而不是为了提高速度吗?

这是我看到的基准表。
基准表

这是 vanillajs 测试代码

 '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>

这是反应测试代码

 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>

基准测试结果站点在这里js-framework-benchmark 结果
和githubsite js-framework-benchmark github

React 使用虚拟 DOM 来增强它的性能。

React 旨在帮助开发人员从称为“组件”的孤立代码片段制作复杂的 UI。 所以它的主要目的是帮助创建大型项目,而不是比 Vanilla JS 更快。 然而,为了提高它的性能,它使用了虚拟 DOM。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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