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