如果您在最近几年中使用过JavaScript框架,那么您可能已经听说过“虚拟DOM快速”这一短语,通常被认为是它比真实DOM更快。 这是一个令人惊讶的弹性模因-例如,人们问Svelte在不使用虚拟DOM时如何快速
什么是虚拟DOM?
在许多框架中,你可以使用使用 render()函数,来构建APP,例如 react 组件
function HelloMessage(props) {
return (
<div className="greeting">
Hello {props.name}
</div>
);
}
没有JSX,你也可以做到
function HelloMessage(props) {
return React.createElement(
‘div‘,
{ className: ‘greeting‘ },
‘Hello ‘,
props.name
);
}
但是结果是一样的-一个代表页面现在外观的对象。 该对象是虚拟DOM。 每当您的应用程序状态更新时(例如,name 属性 更改时),您都会创建一个新的状态。 框架的工作是将新的与旧的进行协调,以找出需要进行哪些更改并将其应用于真实的DOM
关于虚拟DOM性能的误解可追溯到React的发布。在前思想核心团队成员皮特·亨特(Pete Hunt)2013年的开创性演讲《重新思考最佳实践》中,我们学到了以下内容
https://www.youtube.com/watch?v=x7cQ3mrcKaY
?
但是,虚拟DOM操作是对实际DOM的最终操作的补充,唯一可能更快的方法是,如果我们将其与效率较低的框架进行比较(2013年还有很多事情要做!),或反对一个稻草人-替代方案是做某事,而实际上没有人做:
onEveryStateChange(() => {
document.body.innerHTML = renderMyApp();
});
react不是魔术。 就像您可以使用C进入汇编器并击败C编译器一样,您也可以进入原始DOM操作和DOM API调用,并在需要时击败React。 但是,使用C或Java或JavaScript可以将性能提高一个数量级,因为您不必担心…平台的细节。 使用React,您甚至可以不考虑性能而构建应用程序,并且默认状态是快速的。
所以…虚拟DOM慢吗
不完全是。它更像是“虚拟DOM通常足够快”,但有一些警告。
React的最初承诺是,您可以在每个状态更改时重新渲染整个应用程序,而不必担心性能。 实际上,我认为事实并非如此。 如果是这样,就不需要像shouldComponentUpdate这样的优化(这是一种告诉React何时可以安全地跳过组件的方法)
即使使用shouldComponentUpdate,一次完成更新整个应用程序的虚拟DOM仍需要大量工作。 不久前,React团队引入了一种叫做React Fiber的东西,它可以将更新分成较小的块。 这意味着(除其他事项外)更新不会长时间阻塞主线程,尽管它不会减少工作总量或更新所花费的时间
开销来自哪里?
最明显的是,差异并非免费。您必须先将新的虚拟DOM与先前的快照进行比较,然后才能对真实DOM应用更改。以较早的HelloMessage示例为例,假设名称prop从“ world”更改为“ everybody”
两个快照都包含一个元素。在两种情况下都是
?
我们枚举旧的
?
下降到元素中,我们看到文本已更改,因此我们需要更新真实的DOM
在这三个步骤中,只有第三个步骤在这种情况下才有价值,因为-就像绝大多数更新一样-应用程序的基本结构没有改变。如果我们直接跳到步骤3,效率会更高:
if (changed.name) {
text.data = name;
}
(这几乎完全是Svelte生成的更新代码。与传统的UI框架不同,Svelte是一种编译器,它在构建时就知道应用程序中的情况如何变化,而不必等着在运行时完成工作。)
虽然不只是差异
React和其他虚拟DOM框架使用的差异算法速度很快。可以说,组件本身的开销更大。你不会写这样的代码…
function StrawManComponent(props) {
const value = expensivelyCalculateValue(props.foo);
return (
<p>the value is {value}</p>
);
}
因为无论props.foo是否已更改,您都会在每次更新时不小心重新计算值。但是以似乎更有益的方式进行不必要的计算和分配是非常普遍的:
function MoreRealisticComponent(props) {
const [selected, setSelected] = useState(null);
return (
<div>
<p>Selected {selected ? selected.name : ‘nothing‘}</p>
<ul>
{props.items.map(item =>
<li>
<button onClick={() => setSelected(item)}>
{item.name}
</button>
</li>
)}
</ul>
</div>
);
}
在这里,我们会在每次状态更改时生成一个新的虚拟
- 元素数组-每个元素都有自己的内联事件处理程序-不管props.items是否已更改。 除非您对性能不满意,否则不会对其进行优化。 毫无意义。 它足够快。 但是你知道会更快吗? 不这样做。
-
?
即使进行琐碎的工作,也默认不执行不必要的工作的危险在于,您的应用最终将屈服于“千刀相砍”,而没有明显的瓶颈可以立即优化。
Svelte的设计明确可以防止您陷入这种情况
为什么框架使用虚拟DOM
了解虚拟DOM不是一项功能很重要。 这是达到目的的一种手段,最终是声明性的,状态驱动的UI开发。 虚拟DOM非常有价值,因为它使您无需考虑状态转换即可构建应用程序,并且性能通常足够好。 这意味着更少的错误代码,而将更多的时间花费在创造性的任务上,而不是乏味的工作上。
但是事实证明,我们无需使用虚拟DOM就可以实现类似的编程模型-这就是Svelte的用武之地
?