React Diff 算法

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

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 来防止重新渲染大型子树。

来源:原文链接 翻译链接 


js实现合并两个有序数组

给定两个有序整数数组arr1和arr2,将 arr2和arr1进行合并为一个单调非递减的数组,并将其输出。方法一利用循环、方法二直接使用数组的sort方法。

js实现将一个正整数分解质因数

js 把一个正整数分解成若干个质数因子的过程称为分解质因数,在计算机方面,我们可以用一个哈希表来存储这个结果。首先,你需要一个判断是否为质数的方法,然后,利用短除法来分解。

js算法实现_二维数组中的查找

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数

js算法-查找斐波纳契数列中第N个数

所谓的斐波纳契数列是指前2个数是 0 和 1 ,第 i 个数是第 i-1 个数和第i-2 个数的和。下面我们来用js获取菲波那契数列的第N个数为多少:递归、闭包+缓存、直接计算出该数列的值得数组,然后再从数组中取值 、直接使用数学表达式

js求水仙花数_JavaScript可自定义范围打印水仙花数

水仙花数是指一个 n 位正整数 ( n≥3 ),它的每个位上的数字的 n 次幂之和等于它本身(例如:1^3 + 5^3+ 3^3 = 153)。这篇文章主要介绍js实现生成自定义范围内的水仙花数。

js实现生成任意长度的随机字符串

js生成任意长度的随机字符串,包含:数字,字母,特殊字符。实现原理:可以手动指定字符库及随机字符长度,利用Math.round()和Math.random()两个方法实现获取随机字符

js求数组中的最大差值的方法总汇

有一个无序整型数组,如何求出这个数组中最大差值。(例如:无序数组1, 3, 63, 44最大差值是 63-1=62)。实现原理:遍历一次数组,找到最大值和最小值,返回差值

js实现统计一个字符串中出现最多的字母的方法总汇

给出一个字符串,统计出现次数最多的字母。方法一为 String.prototype.charAt:先遍历字符串中所有字母,统计字母以及对应显示的次数,最后是进行比较获取次数最大的字母。方法二 String.prototype.split:逻辑和方法一相同,只不过是通过 split 直接把字符串先拆成数组。

js之反转整数算法

将一个整数中的数字进行颠倒,当颠倒后的整数溢出时,返回 0 ;当尾数为0时候需要进行舍去。解法:转字符串 再转数组进行操作,看到有人用四则运算+遍历反转整数。

js算法_js判断一个字符串是否是回文字符串

什么是回文字符串?即字符串从前往后读和从后往前读字符顺序是一致的。例如:字符串aba,从前往后读是a-b-a;从后往前读也是a-b-a