个人常用JavaScript及React常用优化总结
gmogle
8年前
<h3>JavaScript 插件性能优化及个人react常用优化方法</h3> <p>JavaScript 经常会触发视觉变化。有时是直接通过样式操作,有时是会产生视觉变化的计算,例如搜索数据或将其排序。时机不当或长时间运行的 JavaScript 可能是导致性能问题的常见原因。您应当设法尽可能减少其影响。</p> <p>JavaScript 性能分析可以说是一门艺术,因为您编写的 JavaScript 代码与实际执行的代码完全不像。现代浏览器使用 JIT 编译器和各种各样的优化和技巧来尝试为您实现尽可能快的执行,这极大地改变了代码的动态。</p> <p>注:如果您真的想了解 JIT 的实例,应当查看 Vyacheslav Egorov 提供的 IRHydra2。它显示了当 Chrome 的 JavaScript 引擎 V8 对 JavaScript 代码进行优化时,JavaScript 代码的中间状态。</p> <p>尽管如此,您肯定还是可以做一些事情来帮助您的应用很好地执行 JavaScript。但 Javascript 性能优化绝不是一种书面的技术,借鉴别人的意见和自己平时项目的总结给出以下几条建议:</p> <h3>变量问题</h3> <p>当一个变量被引用的时候,JavaScript将在作用域链中的不同成员中查找这个变量。作 用域链指的是当前作用于下可用变量的集合,它在各种主流浏览器中至少包含两个部分:局部变量的集合和全局变量的集合。</p> <p>简单地说,如果JavaScript引擎在作用域链中搜索的深度越大,那么操作也就会消耗更多的时间。引擎首先从 this 开始查找局部变量,然后是函数参数、本地定义的变量,最后遍历所有的全局变量。</p> <p>因为局部变量在这条链的起端,所以查找局部变量总是比查找全局变量要块。所以当你想要不止一次地使用一个全局变量的时候,你应该将它定义成局部变量,就像这样:</p> <pre> <code class="language-javascript">var dom1 = document.getElementById('id1'), dom2 = document.getElementById('id2');</code></pre> <p>改写成</p> <pre> <code class="language-javascript">var document = document, demo1 = document.getElementById('id1'), demo2 = document.getElementById('id2');</code></pre> <h3>小心的使用闭包</h3> <p>虽然你可能还不知道“闭包”,但你可能在不经意间经常使用这项技术。闭包基本上被认为是JavaScript中的new,当我们定义一个即时函数的时候,我们就使用了闭包,比如:</p> <pre> <code class="language-javascript">document.getElementById('dom').onclick = function(ev) { };</code></pre> <p>闭包的问题在于:根据定义,在它们的作用域链中至少有三个对象:闭包变量、局部变量和全局变量。这些额外的对象将会导致第1和第2个建议中提到的性能问题。</p> <p>闭包对于提高代码可读性等方面还是非常有用的,只是不要滥用它们(尤其在循环中)。</p> <h3>对象的属性和数组元素影响</h3> <p>谈到JavaScript的数据,一般来说有4种访问方式:数值、变量、对象属性和数组元素。在考虑优化时,数值和变量的性能差不多,并且速度显著优于对象属性和数组元素。</p> <p>因此当你多次引用一个对象属性或者数组元素的时候,你可以通过定义一个变量来获得性能提升。(这一条在读、写数据时都有效)</p> <p>虽然这条规则在绝大多数情况下是正确的,但是Firefox在优化数组索引上做了一些有意思的工作,能够让它的实际性能优于变量。但是考虑到数组元素在其他浏览器上的性能弊端,还是应该尽量避免数组查找,除非你真的只针对于火狐浏览器的性能而进行开发。</p> <h3>不要乱碰DOM</h3> <p>不使用DOM是JavaScript优化中另一个很大的话题。经典的例子是添加一系列的列表项:如果你把每个列表项分别加到DOM中,肯定会比一次性加入所有列表项到DOM中要慢。这是因为DOM操作开销很大。</p> <p>Zakas对这个进行了细致的讲解,解释了由于回流(reflow)的存在,DOM操作是非常消耗资源的。回流通常被理解为浏览器重新选渲染DOM树的处理过程。比如说,如果你用JavaScript语句改变了一个div的宽度,浏览器需要重绘页面来适应变化。</p> <p>任何时候只要有元素被添加到DOM树或者从DOM树移除,都会引发回流。使用一个非常方便的JavaScript对象可以解决这个问题——documentFragment,我并没有使用过,但是在Steve Souders也表示同意这种做法之后我感觉更加肯定了。</p> <p>DocumentFragment 基本上是一种浏览器以非可视方式实现的类似文档的片段,非可视化的表现形式带来了很多优点,最主要的是你可以在 documentFragment 中添加任何结点而不会引起浏览器回流。</p> <h3>不要在数组中挖得太深</h3> <p>另外,程序员应该避免在数组中挖得太深,因为进入的层数越多,操作速度就越慢。</p> <p>简单地说,在嵌套很多层的数组中操作很慢是因为数组元素的查找速度很慢。试想如果操作嵌套三层的数组元素,就要执行三次数组元素查找,而不是一次。</p> <p>因此如果你不断地引用 foo.bar, 你可以通过定义 var bar = foo.bar 来提高性能。</p> <h3>定时器的使用</h3> <p>如果针对的是不断运行的代码,不应该使用setTimeout,而应该是用setInterval,因为setTimeout每一次都会初始化一个定时器,而setInterval只会在开始的时候初始化一个定时器。</p> <pre> <code class="language-javascript">var timeoutTimes = 0; function timeout() { timeoutTimes++; if (timeoutTimes < 10) { setTimeout(timeout, 10); } } timeout(); //可以替换为: var intervalTimes = 0; function interval() { intervalTimes++; if (intervalTimes >= 10) { clearInterval(interv); } } var interv = setInterval(interval, 10);</code></pre> <h3>React上的性能优化</h3> <p>对于小型react前端应用,最好的优化就是不优化因为React本身就是通过比较虚拟DOM的差异,从而对真实DOM进行最小化操作,小型React应用的虚拟DOM结构简单,虚拟DOM比较的耗时可以忽略不计。而对于复杂的前端项目,我们所指的渲染性能优化,实际上是指,在不需要更新DOM时,如何避免虚拟DOM的比较。</p> <ol> <li> <p>react组件的生命周期</p> <pre> <code class="language-javascript">工欲善其事,必先利其器。理解react的组件的生命周期是优化其渲染性能的必备前提。我们可以将react组件的生命周期分为3个大循环:挂载到DOM、更新DOM、从DOM中卸载。React对三个大循环中每一步都暴露出钩子函数,使得我们可以细粒度地控制组件的生命周期。</code></pre> <ul> <li> <p>挂载到DOM</p> <pre> <code class="language-javascript">组件首次插入到DOM时,会经历从属性和状态初始化到DOM渲染等基本流程,可以通过下图所示</code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/8aca606aa46c56bda4a24a0ce8c8ab9a.png"></p> </li> </ul> </li> </ol> <pre> <code class="language-javascript">必须注意的是,挂载到DOM流程在组件的整个生命周期只有一次,也就是组件第一次插入DOM文档流时。在挂载到DOM流程中的每一步也有相应的限制:</code></pre> <ul> <li> <p>更新DOM</p> <pre> <code class="language-javascript">组件挂载到DOM后,一旦其props和state有更新,就会进入更新DOM流程。同样我们也可以通过一张图清晰的描述该流程的各个步骤:</code></pre> <pre> <code class="language-javascript">//getDefaultProps()和getInitialState()中不能获取和设置组件的state。 //render()方法中不能设置组件的state。</code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/0fbeee1a936e57237541a8736dfa1b6f.png"></p> <pre> <code class="language-javascript">componentWillReceiveProps()提供了该流程中更新state的最后时机,后续的其他函数都不能再更新组件的state了。我们尤其需要注意的是shouldComponentUpdate函数,它的结果直接影响该组件是不是需要进行虚拟DOM比较,我们对组件渲染性能优化的基本思路就是:在非必要的时候将shouldComponentUpdate返回值设置为false,从而终止更新DOM流程中的后续步骤。</code></pre> </li> <li> <p>从DOM中卸载</p> <pre> <code class="language-javascript">从DOM中卸载的流程比较简单,React只暴漏出componentWillUnmount,该函数使得我们可以在DOM卸载的最后时机对其进行干预。</code></pre> </li> </ul> <p>2、性能分析</p> <pre> <code class="language-javascript">合理的使用shouldComponentUpdate()可以在很大程序上优化应用。但在实际情况下,应用往往在沙箱或是开发环境中运行的非常快,但生产环境则表现的不尽人意。这时,我们需要对应用进行性能分析,然后再有针对性的在shouldComponentUpdate()中进行优化。 React 提供了性能分析插件React.addons.Perf,它让我们可以在需要检测的代码起始位置分别添加Perf.start()和Perf.stop(),并可以通过Perf.printInclusive()方法打印花费时间,然后我们可以结合数据做进一步的分析。 React.addons.Perf插件的详细用法,可以查看官方文档。</code></pre> <p>3、 借助react Key标识组件</p> <pre> <code class="language-javascript">key属性在组件类之外提供了另一种方式的组件标识。通过key标识我们可以组件如:顺序改变、不必要的子组件更新等情况下,告诉React 避免不必要的渲染而避免性能的浪费。</code></pre> <p>如,对于如一个基于排序的组件渲染</p> <pre> <code class="language-javascript">var items = sortBy(this.state.sortingAlgorithm, this.props.items); return items.map(function(item){ return <img src={item.src} /> });</code></pre> <p>当顺序发生改变时,React 会对元素进行diff操作,并改img的src属性。显示,这样的操作效率是非常低的。这时,我们可以为组件添加一个key属性以唯一的标识组件:</p> <pre> <code class="language-javascript">return <img src={item.src} key={item.id} /></code></pre> <p> </p> <p>来自:https://segmentfault.com/a/1190000008704275</p> <p> </p>