这篇文章只是个人理解,有什么差异和谬误还望大家指出:
不知道是从什么时候开始,写JavaScript的时候,脑袋里面就会一直回响着一句话,尽量避免DOM操作,原因是DOM操作比较消耗性能,特别是在复杂的DOM结构中进行DOM操作。而当我们使用各种各样前端模板引擎的时候,更是无法避免不停地操作DOM。
虚拟DOM
React出于性能的考虑,为了避免频繁操作DOM,采用了虚拟DOM结构(virtual DOM):
每当虚拟DOM树发生变化树发生变化时,React会将当前DOM树和之前的虚拟DOM树进行diff算法对比,得到虚拟DOM结构的区别,然后仅仅渲染差异部分。
var MyComponent = React.createClass({ render: function() { if (this.props.first) { return <div className="first"><span>A Span</span></div>; } else { return <div className="second"><p>A Paragraph</p></div>; } } });
这里MyComponent执行render方法的结果并不是一个真实的DOM节点,而是一个轻量级的JavaScript对象,即虚拟DOM。
如果我们先插入组件:<MyComponent first={true} />;
React会创建真实DOM节点:<div className="first"><span>A Span</span></div>将之前的组件替换为:<MyComponent first={false} />;
React会替换之前节点的属性:className="first"为className="second",同时替换子节点<span>A Span</span>为<p>A Paragraph</p>最后移除组件;
React会移除节点<div className="second"><p>A Paragraph</p></div>
如果将所有虚拟节点进行改变前后的diff算法比较,这同样是一件消耗性能的过程(两个树的比较复杂度为O(n^3)),React采用了以下优化方案,将性能优化到O(n)。
分层比较
React将虚拟树进行分层比较,这是因为节点操作很少存在跨层级的(比如将子节点移动到父节点外,变成父节点的兄弟节点),大多操作多是在兄弟节点之间的。如下图中,比较只会在相同颜色的兄弟节点之间进行,一旦节点被删除,则所有子节点都会被删除,不会进行比较。
节点列表
假设有这样的情况,一个组件初始化时渲染了5个子节点,第二个时钟周期时向这5个子节点中间插入一个新的组件。这时,如果直接对比前后两个子节点树,新插入节点之后的所有子节点都会被重新渲染(C被替换为F,D被替换为C,E被替换为D),因为他们和之前的节点树不匹配。如同下图的情况:
React在组件插入的过程中,只会将同级子节点按前后顺序排列起来,但是,如果给予每个子节点一个唯一的key值,这样每个子节点都能找到与之对应的节点进行比较。
组件层面
节点diff算法只会在相同的组件类型上进行,意味着如果一个<Header>组件被替换成了<ExampleBlock>,这时前后节点树不会进行对比。React做出这样的取舍是因为耗费大量计算去匹配两个几乎不会相似的组件是一种浪费。而事实上,大多数用户会用大量的div去构建一个节点树,React在不会匹配不同class的组件。
参考文章: http://www.infoq.com/cn/articles/react-dom-diff?from=timeline&isappinstalled=0