虚拟dom到真实dom
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了虚拟dom到真实dom相关的知识,希望对你有一定的参考价值。
参考技术A 因为浏览器渲染真实dom的时候,根据html和css进行解析-渲染成页面,对于浏览器来说,它会一个一个dom安先后顺序来进行解析渲染,不会进行汇总比较,或者是因为考虑的真实的dom太复杂了,没法进行快速的比较得到最小差异化。而虚拟dom简单来说就是一开始用js画了一个假的dom结构,映射真实的dom,去除一些我们不用去关注的事情。然后在之后更新dom的时候,先根据这些变更来得出最小的差异化。再把这些差异一次写入真实dom,性能会有很大的提升。
从虚拟dom到真是dom,找到渲染前后真正发生变化的地方,这就是diff算法,考虑到很少进行跨层移动,所以用平层对比,时间复杂度从O(n^3)缩短为O(n),在对比过程中直接对真实dom更新。变更一般有三种: 文本 ,节点属性,节点变更,增删节点(绑定key值的作用)
ps html和css解析成的render树,每一个节点renderObject,浏览器会根据原则
1、它是页面的根对象
2、它具有显式的CSS位置属性(相对、绝对或转换)
3、它是透明的
4、是否有溢出、alpha掩码或反射
5、有一个CSS过滤器
6、对应于具有3D (WebGL)上下文或加速2D上下文的画布元素
7、对应于一个视频元素
来生成renderLayer,每一个renderObject会指向一个renderLayer,直接或者间接指向父节点的renderLayer。当浏览器渲染的时候renderLayer决定了渲染的层级,renderObject决定了渲染的内容
什么时候虚拟 DOM 比真实 DOM 快?
【中文标题】什么时候虚拟 DOM 比真实 DOM 快?【英文标题】:When is virtual DOM faster than real DOM? 【发布时间】:2021-03-10 12:37:22 【问题描述】:我知道如果在vanilla js中更改DOM,整个浏览器每次都会重新布局和重新绘制。
所以在元素多、变化频繁的单页应用中,vanilla js 会变慢。
但我最近在 Benchmark 表中看到 vanilla js 比使用虚拟 DOM 时的反应快得多,即使更改了大量数据也是如此。
那么,使用虚拟 DOM 的原因是为了自动化和方便开发人员而不是为了提高速度吗?
这是我看到的 Benchmark 表。benchmark table
这是 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((e,i) => 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 result 和github站点 js-framework-benchmark github
【问题讨论】:
当你做基准测试时,你总是能得到更好的 vanilla js 结果。因为使用虚拟 dom,您完成了 VDOM + DOM 更改,然后进行测量。当您进行一些小的更改并需要检测其他元素的更改(例如三个)时,好处就来了。 【参考方案1】:React 使用虚拟 DOM 来增强 它的性能。
React 旨在帮助开发人员从称为“组件”的孤立代码片段制作复杂的 UI。所以它的主要目的是帮助创建大型项目,而不是比 Vanilla JS 更快。但是为了提高性能,它使用了虚拟 DOM。
【讨论】:
以上是关于虚拟dom到真实dom的主要内容,如果未能解决你的问题,请参考以下文章
Vue原理解析(五):彻底搞懂虚拟Dom到真实Dom的生成过程