1,加载和执行
Javascript在浏览器中的性能,可以认为是开发者所面临的最严重的可用性问题。多数浏览器使用单一进程来处理用户界面UI刷新和Javascript脚本执行,so,同一时刻只能做一件事。JS执行过程耗时越久,浏览器等待响应的时间就越长。
管理浏览器中的JS代码是个棘手的问题,每次遇到<script>
标签,页面都必须停下来等待代码下载并执行,然后继续处理其他部分。尽管如此,还是有几种方法能减少JS对性能的影响。
</body>
闭合之前,将所有的<script>
标签放到页面底部。这能确保在脚本执行前页面已经完成了渲染。- 合并脚本。页面中的
<script>
标签越少,加载也就越快,响应也更迅速。无论外链文件还是内嵌脚本都是如此。 有多种无阻塞下载Javascript的方法:
- 使用
<script>
标签的defer属性; - 使用动态创建的
<script>
元素来下载并执行代码; - 使用XHR对象下载Javascript代码并注入页面中。
- 使用
通过以上策略,可以极大提高那些需要使用大量Javascript的web应用的实际性能。
2,数据存取
计算机科学中有一个经典问题是通过改变数据的存储位置来获得最佳的读写性能,数据存储的位置关系到代码执行过程中数据的检索速度。
Javascript中,数据存储的位置会对代码整体性能产生重大的影响。数据存储共有4种方式:字面量、变量、数组项、对象成员。它们有着各自的性能特点。
- 访问字面量和局部变量的速度最快,相反,访问数组元素和对象成员相对较慢。
- 由于局部变量存在于作用域链的起始位置,因此访问局部变量比访问跨作用域变量更快。变量在作用域链中的位置越深,访问所需时间就越长。由于全局变量总处在作用域链的最末端,因此访问速度也是最慢的。
- 避免使用with语句,因为它会改变执行环境作用域链。同样,try-catch语句中的catch子句也有同样的影响,因此也要小心使用。
- 嵌套的对象成员会明显影响性能,尽量少用。
- 属性或方法在原型链中的位置越深,访问它的速度也越慢。
- 通常来说,你可以通过把常用的对象成员、数组元素、跨域变量保存在局部变量中来改善Javascript性能,因为局部变量访问速度更快。
通过以上策略,可以极大提高那些需要使用大量Javascript的web应用的实际性能。
3,DOM编程
用脚本进行DOM操作的代价很昂贵,它是富WEB应用中最常见的性能瓶颈。
访问和操作DOM是现代WEB应用的重要部分。但每次穿越连接ECMAScript和DOM两个岛屿之间的桥梁,都会被收‘过桥费’。为了减少DOM编程带来的性能损失,请记住以下几点:
- 最小化DOM访问次数,尽可能在Javascript端处理。
- 如果需要多次访问某个DOM节点,请使用局部变量存储它的引用。
- 小心处理HTML集合,因为它实时联系着底层文档。把集合的长度缓存到一个变量中,并在迭代中使用它。如果需要经常操作集合,建议把它拷贝到一个数组中。
- 如果可能的话,使用速度更快的API,比如querySelectorAll()和firstElementChild。
- 要留意重绘和重排;批量修改样式时,“离线”操作DOM树,使用缓存,并减少访问布局信息的次数。
- 动画中使用绝对定位,使用拖放代理。
- 使用事件委托来减少事件处理器的数量。
4,算法和流程控制
代码的整体结构是影响运行速度的主要因素之一。
Javascript和其他编程语言一样,代码的写法和算法会影响运行时间。与其他语言不同的是,Javascript可用资源有限,因此优化技术更为重要。
注:由于Javascript是解释性语言,与编译性语言不同的是,它无需编译,而是将代码以字符串的形式交给Javscript引擎来执行。因此,代码性能在一定程度上取决于客户端浏览器的Javascript引擎。
- for、while和do-while循环性能特性相当,并没有一种循环类型明显快于或慢于其他类型。
- 避免使用for-in循环,除非你需要遍历一个属性数量未知的对象。
- 改善循环性能的最佳方式是减少每次迭代的运算量和减少循环迭代次数。
- 通常来说,switch总是比if-else快,但并不总是最佳解决方案。
- 在判断条件较多时,使用查找表比if-else和switch更快。
- 浏览器的调用栈大小限制了递归算法在Javascript中的应用;栈溢出错误会导致其他代码中断运行。
- 如果你遇到栈溢出错误,可将方法改为迭代算法,或使用Memoization来避免重复计算。
运行的代码数量越大,使用这些策略所带来的性能提升也就越明显。
5,字符串和正则表达式
密集的字符串操作和草率地编写正则表达式可能产生严重的性能障碍,本章提供的建议会帮助你避免这些常见的陷阱。
- 当连接数量巨大或尺寸巨大的字符串是,数组合并是唯一在IE7以及更早版本中性能合理的方法。这条忽略吧,现在谁还考虑IE7,IE8?
- 如果不需要考虑IE7及更早版本的性能,数组项合并是最慢的字符串连接方法之一。推荐使用简单的+和+=操作符替代,避免不必要的中间字符串。
- 回溯既是正则表达式匹配功能的基本组成成分,也是正则表达式的低效之源。
- 回溯失控发生在正则表达式本应快速匹配的地方,但因为某些特殊的字符串匹配动作导致运行缓慢甚至浏览器崩溃。避免这个问题的办法是:使相邻的字元互斥,避免嵌套量词对同一字符串的相同部分多次匹配,通过重复利用预查的原子组去除不必要的回溯。
- 提高正则表达式效率的各种技术手段会有助于正则表达式更快地匹配,并在非匹配位置上花更少的时间。
- 正则表达式并不总是完成工作的最佳工具,尤其当你只搜索字面字符串的时候。
- 尽管有许多方法可以去除字符串的首尾空白,但使用两个简单的正则表达式(一个去除头部空白,另一个去除尾部空白)来处理大量字符串内容能提供一个简洁而且跨浏览器的方法。从字符串末尾开始循环向前搜索第一个非空白字符,或者将此技术同正则表达式结合起来,会提供一个更好的替代方案,它很少受到字符串长度影响。
6,快速响应的用户界面
JS在浏览器中大多是单线程的,每个时刻只能执行其中一种操作,这意味着当JS代码正在执行时用户界面无法响应输入,反之亦然。高效地管理UI线程就是要确保JS不能运行太长时间,以免影响用户体验。最后,请牢记如下几点:
- 任何JS任务都不应当执行超过100ms。过长的运行时间会导致UI更新出现明显的延迟,从而对用户体验产生负面影响。
- JS运行期间,浏览器响应用户交互行为存在差异。无论如何,JS长时间运行将导致用户体验变得混乱和脱节。
- 定时器可以用来安排代码延迟执行,它使得你可以把长时间运行脚本分解成一系列的小任务。
- WEB WORKERS是新版浏览器支持的特性,它允许你在UI线程外部执行JS代码,从而避免锁定UI。
WEB应用越复杂,积极主动地管理UI线程就越重要。即使JS代码再重要,也不应该影响用户体验。
7,Ajax
Ajax是高性能Javascript的基础。它可以通过延迟下载体积较大的资源文件来使得页面加载速度加快。它通过异步的方式在客户端和服务端之间传输数据,从而避免页面资源一窝蜂地下载。
注:请求数据
有五种常用技术用于向服务器请求数据:
- XMLHttpRequest (XHR)
- Dynamic script tag insertion 动态脚本注入
- iframes
- Comet
- Multipart XHR
在现代高性能JS中使用的三种技术是:XHR、动态脚本注入和multipart XHR。
高性能的Ajax包括以下方面:了解你项目的具体需求,选择正确的数据格式和与之匹配传输技术。
作为数据格式,纯文本和HTML只适用于特定场合,但它们可以节省客户端的CPU周期。XML被广泛应用而且支持良好,但是它十分笨重且解析缓慢。JSON是轻量级的,解析速度快(被视为原生代码而不是字符串),通用性与XML相当。字符分割的自定义格式十分轻量,在解析大量数据集时非常快,但需要编写额外的服务端构造程序,并在客户端解析。
当从页面当前所处的域下请求数据时,XHR提供了最完善的控制和灵活性,尽管它会把接收到的所有数据当成一个字符串,且这有可能降低解析速度。另一方面,动态脚本注入允许跨域请求和本地执行JS和JSON,但它的接口不那么安全,而且还不能读取头信息或相应代码。Multipart XHR可以用来减少请求数,并处理一个响应中的各种文件类型,但是它不能缓存接收到的响应。当需要发送数据时,图片信标是一种简单而且有效的方法。XHR还可以用POST方法发送大量数据。
除了这些格式和传输技术,还有一些准则有助于加速你的Ajax。
- 减少请求数,可通过合并JS和CSS文件,或使用MXHR。
- 缩短页面的加载时间,页面主要内容加载完成后,用Ajax获取那些次要的文件。
- 确保你的代码错误不会输出给用户。并在服务端处理错误。
- 知道何时使用成熟的Ajax类库,以及何时编写自己的底层Ajax代码。
Ajax为提升你的网站的潜在性能提供了广阔的空间,因为许多网站大量使用异步请求,而且它还提供了一些与它无关的问题的解决方案,比如有过多的资源需要加载。对XHR创造性地使用是一个反应迟钝且平淡无奇的页面与相应快速且高效的页面的区别所在;是一个用户痛恨使用的站点与用户迷恋的站点的却别所在。
8,编程实践
Javascript提出了一些独一无二的性能挑战,这与你组织代码的方式油有关。随着WEB应用变得越来越高级,包含的JS代码也越来越多,各种模式和反模式也逐渐出现。为了编写更高效的代码,请牢记以下编程实践:
- 避免使用eval()和Function()构造器来避免双重求值带来的性能消耗。同样的,给setTimeout()和setInterval()传递函数而不是字符串作为参数。
- 尽量使用直接量创建对象和数组。直接量的创建和初始化都比非直接量形式要快。
- 避免做重复的工作。当需要检测浏览器时,可使用延迟加载或条件预加载。
- 在进行数学计算时,考虑使用直接操作数字的二进制形式的位运算。
- JS的原生方法总会比你写的任何代码都要快。尽量使用原生方法。
高性能javascript涵盖了大量的优化技术和方法,当把这些方案应用在那些被频繁运行的代码上时,你将会看到显著的性能提升。
9,构建并部署高性能Javascript应用
构建与部署的过程对基于JS的WEB应用的性能有着巨大影响。这个过程中最重要的步骤有:
- 合并JS文件以减少HTTP的请求数。
- 使用工具压缩JS文件。
- 在服务器端压缩JS文件(如Gzip编码压缩)。
- 通过正确设置HTTP响应头来缓存JS文件,通过向文件名增加时间戳或者MD5字符串来避免缓存问题。
- 使用CDN提供JS文件,CDN不仅可以提升性能,它也为你管理文件的压缩与缓存。
所有这些步骤都应该自动化处理,可以使用公开的工具,也可以使用定制的工具来满足你的特定需求。如果你使得构建过程工作起来,你将会显著提高那些依赖大量JS的WEB应用或网站的性能。
10,工具
当网页或WEB应用变慢时,分析从网络下载的资源以及分析脚本的运行性能能让你专注于那些最需要优化的地方。
- 使用网络分析工具找出加载脚本和页面中其他资源的瓶颈,这会帮助你决定哪些脚本需要延迟加载,或者需要进一步分析。
- 尽管传统的经验告诉我们要尽量减少HTTP请求数,但把脚本进可能延迟加载可以加快页面渲染速度,给用户带来更好的体验。
- 使用性能分析工具找出脚本运行过程中速度慢的地方,检查每个函数所消耗的时间,以及函数被调用的次数,通过调用栈自身提供的一些线索来找出需要集中精力优化的地方。
- 尽管耗费的时间和调用次数通常是数据中最有价值的部分,但仔细观察函数的调用过程,你也许会发现其他优化目标。
这些工具会帮助你深入了解你的代码在那些通常你比较陌生的编程环境下是如何运行的。在开始优化工作之前先使用它们,以确保开发时间用在刀刃上。