大部分Javascript引擎的优化是为了快速抢占市场而不是性能,所以JavaScript一直很自然地被认为是瓶颈。但通常情况下,瓶颈不是JavaScript而是DOM,所以倒腾脚本收效甚微。
尽量避免倒腾脚本,要正确并清晰的编写程序。倒腾往往越整越乱,它只会让程序更容易产生bug。
避免使用那些传说中能让程序更快的奇技淫巧,除非你能证明它们能给应用带来明显的质量提升。大多数情况下,他们只会降低代码质量,而不会有多大提升。不要纠结于特定浏览器的怪癖,浏览器仍在发展,最终会支持更好的编码实践。
如果感觉必须要捣腾,首先要评估。我们对程序真正开销的直觉往往是错的。只有通过评估,才能有把握对性能产生积极的影响。
创建快速响应的web应用-
别把运行实践可能很长的低性能代码引入到网页中。
拆分初始化负载-
通过拆分来节省下载量
无阻塞加载脚本-
通常,大多数浏览器是并行下载组件的,但对于外部脚本并非如此。当浏览器开始下载外部脚本时,在脚本下载、解析并执行完毕之前,不会开始下载任何其他内容。
浏览器在下载和执行脚本时出现阻塞的原因在于,脚本可能会改变页面或JavaScript的命名空间,他们会对后续内容造成影响。
很明显脚本必须按顺序执行,但没有必要按顺序下载。
我们真正想要的是让脚本与所有其他组件并行下载,而且希望在所有浏览器中实现。
- XHR Eval
- XHR 注入(XHR Injection)
- Script in Iframe
- Script DOM Element
- Script Defer
- document.write Script Tag
最佳方案
没有独立的最佳方案,真正的最佳方案取决于需求:
在决策树里有6中可能的结果:
- 不同域、无序
- 不同域、保持顺序
- 同域、无序、无忙指示器
- 同域、无序、有忙指示器
- 同域、保持顺序、无忙指示器
- 同域、保持顺序、有忙指示器
整合异步脚本
当异步加载的外部脚本与行内脚本之间存在代码依赖时,我们必须通过一种保持执行顺序的方法来整合这两个脚本。很显然,并没有一个能跨所有浏览器实现的简单方法。本章提出了这个问题,并给出一些解决方案:
代码示例:menu.js
竞争状态
异步保持顺序
**Script Onload技术是整合异步加载外部脚本和行内脚本的首选。**他不引用任何外部脚本里的标识符,所以维护更简单。行内代码可以在外部脚本加载完毕后立即执行。事件处理也非常简单。
多个外部脚本
Managed XHR技术解决了跨主流浏览器的兼容性问题。但是由于XMLHTTPRequest的同源规则,如果外部脚本部署在主页面不同的域下,该技术就无法工作。所以当脚本和页面不同域时,我们选择的方案是DOM Element 和 Doc Write技术。
通用解决方案
现实工作中的异步加载
布置行内脚本
行内脚本虽然不会产生额外的HTTP请求,但会阻塞页面上资源的并行下载,还会阻塞逐步渲染。
把行内脚本移至底部
使用异步回调启动JavaScript的执行
setTimeout(function(){}, 0);
setTimeout(function(){}, 250);使用script的defer属性
script的defer属性只有Internet Explorer和Firefox3.1+支持,所以更好的方式是使用setTimeout
编写高效的JavaScript
JavaScript的执行速度取决于代码的编写方式。
- 管理作用域非常重要,因为存取非局部变量要比局部变量耗时更多。尽量避免使用会增长作用域链的结构,比如使用with语句和try-catch语句中的catch从句。如果非局部变量的使用超过一次,那么为了降低性能损耗,就应该将它存储在一个局部变量中。
- 存储和读取数据的方式对脚本的性能影响极大。字面量和局部变量总是最快的;存取数组元素和对象属性会引起性能损耗。如果数据元素或对象属性使用超过一次,那么为了提高存取速度,就应该将它存储到一个局部变量中。
- 流控制也是影响脚本执行速度的一个重要因素。条件判断有3中处理方式:if语句、switch语句和数组查找。if语句适用于少量离散值或一段区间值的判断;switch语句最好用于对3~10个离散值的判断;数组查找在吹大量离散值时效率最高。
- 在Javascript中,循环经常会成为性能瓶颈。为了使循环最高效,可以采用倒序的方式来处理元素,也就是在控制条件中,将迭代变量和0作比较。相比非0值,这种方式要快得多,从而显著提升数组的处理速度。如果必须要进行大量的迭代,还可以考虑使用Duff策略来提高执行速度。
- 谨慎使用HTMLCollection对象。每次存取这类对象的属性,都会重新查询DOM中匹配的节点。为了避免这种高昂的开销,只有在必要时才存取HTMLCollection对象,并将经常存取的值(例如length属性)存储在局部变量中。
- 常见的字符串操作可能会带来意料之外的性能问题。Internet Explorer处理字符串链接的速度比其他浏览器要慢很多,但这没什么大不了,除非你要一次执行1000次以上的字符串链接。你可以对Internet Explorer处理字符串链接的方式进行优化:先将所有要链接的字符串存储到数组中,然后调用join()方法合并他们。去除字符串两端的空白可能很耗时,这取决于字符串的大小。如果脚本中经常需要去除字符串两端的空白,那请确保使用最优算法。
- 浏览器会限制JavaScript可以运行的最长时间,有些会以执行语句的数量作为判断条件,有些则会控制JavaScript引擎执行的总时间。你可以使用定时器将任务拆分执行,从而绕开这些限制,避免浏览器弹出终止脚本运行的警告。