簡體   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