React Diff 算法

时间: 2017-11-27阅读: 256标签: 算法

React 是 facebook 出的一个前端框架. 设计的关键处就是性能问题。在本文中,我主要是介绍 Diff 算法以及 React 渲染 ,这样你可以更好的优化你的应用程序。

Diff 算法

再介绍算法之前,我们先来了解一下 react 是怎么工作的。

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>; 
        } }
    });

在你构思着要UI 的时候。要知道最重要的一点是:渲染的结果不是一个真实 DOM 。 只是个普通的 JavaScript 对象. 我们就把它叫做虚拟 DOM.

React 将使用这个方法试着用最快捷的步骤从前一个渲染到下一个。例如, 如果我们挂载 , replace it with, 然后卸载它, DOM 的结果会是这种情况:

None to first

  • Create node: A Span

First to second

  • Replace attribute: className="first" by className="second"
  • Replace node: A Span by A Paragraph

Second to none

  • Remove node: A Paragraph

Level by Level

可以看到,在任意两棵树上找到最少修改次数为 O(n^3)。 这不是我们想要的. React 就使用了一种简单高效的方式来处理这个修改,为 O(n).

React 只是一级一级的调用这棵树。 在影响极小的性能代价下大大的降低来复杂度,在 Web 应用中移动不同的 DOM 树是及其罕见的。 通常只会在子节点上进行横向移动。


List

假设我们有一个组件,一次渲染五个组件并在下一次渲染时候插入在其中插入一个新组件。这样很难比较这两个列表中组件之间的映射关系

默认情况下,React 会把前一个列表组件和下一个列表组件相关联起来。你可以通过添加 key的方式,去帮助 React 找到映射。在实际中,这样子元素就会容易的被找出来。


Components

通常,一个 React App 会有很多用户自己定义的 div 元素,这样元素构成一颗很复杂的树。由于 React 只会比较有同一个类的组件,因此 diff 算法会带一些额外的组件信息。

例如假如是 is replaced by an, React 将删除标题组件并创建一个新的组件。 我们不需要太多的时间来匹配两个不太可能有相似之处的组件。


Event Delegation

在 DOM 节点上进行事件监听会使程序变得缓慢而且会耗费大量的内存。 因此,React 就采用了事件委托的方式。React 更进一步的实现符合 W3C 标准的事件。 Internet Explorer 8 的事件错误已经成为历史,所有事件名称在浏览器中都是一致的。

让我解释一下它是如何实现的。 单个事件侦听器附加到文档的根目录。 当事件被触发时,浏览器给我们目标 DOM 节点。 为了通过 DOM 传播事件,React 将不会在虚拟 DOM 上迭代。

相反,我们使用每个 Reac t组件都有唯一的 ID。 我们可以使用简单的字符串操作来获得所有父组件的 ID。 通过将事件侦听器存储在哈希映射中,我们发现它比将它们附加到虚拟 DOM 更好。 以下是通过虚拟DOM分派事件时发生的情况的示例。

// dispatchEvent('click', 'a.b.c', event) clickCaptureListeners['a'](event); clickCaptureListeners['a.b'](event); clickCaptureListeners['a.b.c'](event); clickBubbleListeners['a.b.c'](event); clickBubbleListeners['a.b'](event); clickBubbleListeners['a'](event);

浏览器为每个事件和每个侦听器创建一个新的事件对象。 这有很好的性能,你可以保留事件对象的引用,甚至修改它。 但是,这意味着要进行大量的内存分配。 在启动时反应分配这些对象的池。 每当需要事件对象时,都会从该池中重用。 这大大减少了垃圾收集。

Rendering

Batching

每当你调用一个组件的 setState 时,React 就会把它标记为脏元素。 在事件循环结束时,React 查看所有有脏元素的组件并重新渲染它们。

这个批处理意味着在一个事件循环期间,DOM 正在被更新。 这个属性是构建高性能应用程序的关键。 在React应用程序中,默认情况下会使用它。


Sub-tree Rendering

当调用setState时,组件会重新建立其子组件虚拟 DOM。 如果你在根元素上调用setState,那么整个React应用程序就会被重新渲染。 所有的组件,即使它们没有改变,都会调用它的“渲染”方法。 这听起来可怕,效率低下,但在实践中,这影响也不大,因为我们并没有触及到实际的DOM。。。。。(其实还是蛮大的啊,有些组件会多次渲染啊,shouldUpdate 优化)

首先,我们正在讨论显示用户界面。 由于屏幕空间有限,通常一次需要显示数百到数千个元素的订单。 幸好 JavaScript 已经有足够快的业务反应,使得整个界面处于可管理状态。

另一个重要的一点是,在编写 React 代码时,最好每次改变时都不要在根节点上调用 setState。 您可以在接收到更改事件的组件或它的父组件上调用。你很少会在最顶端去处处理逻辑,这就意味着渲染只会发生在组件自己的节点上。


Selective Sub-tree Rendering

最后,你有可能阻止一些子树重新渲染。 如果您在组件上实现以下方法:

boolean shouldComponentUpdate(object nextProps, object nextState)

基于组件的前一个和下一个状态,你可以告诉 React 这个组件没有改变,没有必要重新渲染它。 如果执行得当,这可以给你巨大的性能改进。(还是应该从根本上去避免组件的渲染问题。。。把组件当作最好一个组件写。。。。。)

为了能够使用它,你必须要比较 JavaScript 的对象。 如果浅比较的话,就会有很多问题提出来, 如果深的话我们应该使用不可变的数据结构或者做更深层的拷贝。

而且你要记住,这个函数会一直被调用,所以你要确保计算花费的时间比启发式计算所需的时间少,而不是渲染组件的时间, 渲染不是严格需要的。.


Conclusion

我们已经知道很长一段时间触摸DOM是昂贵的,你应该批量写和读操作,事件委托更快...

人们仍然在谈论他们,因为在实践中,他们很难在普通的JavaScript代码中实现。 React的突出之处在于所有这些优化都是默认进行的。 这使得在脚下拍摄自己很难,并使您的应用程序变慢。

React 的性能优化问题也很容易理解:每个setState重新渲染整个子树。 如果要压缩性能,请尽可能调用 setState,并使用shouldComponentUpdate 来防止重新渲染大型子树。

来源:原文链接 翻译链接 


Tracking.js_ js人脸识别前端代码/算法框架

racking.js 是一个独立的JavaScript库,实现多人同时检测人脸并将区域限定范围内的人脸标识出来,并保存为图片格式,跟踪的数据既可以是颜色,也可以是人,也就是说我们可以通过检测到某特定颜色,或者检测一个人体/脸的出现与移动,来触发JavaScript 事件。

原生Js获取数组中最长的连续数字序列的方法

给定一个无序的整数序列, 找最长的连续数字序列。例如:给定[100, 4, 200, 1, 3, 2],最长的连续数字序列是[1, 2, 3, 4]。此方法不会改变传入的数组,会返回一个包含最大序列的新数组。

js从数组取出 连续的 数字_实现一维数组中连续数字分成几个连续的数字数组

使用原生js将一维数组中,包含连续的数字分成一个二维数组,这篇文章分2种情况介绍如何实现?1、过滤单个数字;2、包含单个数字。

程序员必须知道的10大基础实用算法及其讲解

程序员必须知道的10大算法:快速排序算法、堆排序算法、归并排序、二分查找算法、BFPRT(线性查找算法)、DFS(深度优先搜索)、BFS(广度优先搜索)、Dijkstra算法、动态规划算法、朴素贝叶斯分类算法

js洗牌算法:javascript数组随机打乱顺序的实现方法

有一个数组,我们需要通过js对数组的元素进行随机排序,然后输出,这其实就是洗牌算法,首页需要从元素中随机取一个和第一元进行交换,然后依次类推,直到最后一个元素。

原生js实现冒泡排序算法,javascript冒泡排序

javascript冒泡排序的实现,冒泡排序是一个非常常见的排序算法,对于一个数组,每趟排序时依次比较两个相邻的数,如果他们的顺序错误就交换两数位置。