始终坚持只要能在构建时完成的,不要留在运行时做。
文章内容主要是针对 js 部分的性能优化,分别从加载、数据存取、DOM渲染、算法、AJAX方面来介绍常见的优化方式。概括性来说就是加快响应速度,为此应尽可能地减少请求,精简内容。另外合理利用性能分析工具,针对性地找出能进行优化的部分。
加载
- script 脚本文件放在 body 底部。
- 脚本在解析时是逐个文件先下载后解析,放在头部会导致解析页面堵塞。
- 尽量少使用 script 标签。
- 可以减少 HTTP 请求数,比如多个脚本文件可以合并为同一个文件。
- 把文件合并到链接,请求一个链接时再分别下载对应的文件。
- 压缩文件。Accept-Encoding 支持的格式有:gzip, compress, deflate, identify
- GZip 压缩(只能压缩文本文件)。
- smasher(使用PHP写成的一个小工具)
- 压缩 js 文件。
- JSMin
- YUI Compressor 实现方式:局部变量变短,对象的属性名引号去掉,中括号换为点,替换其转义字符,合并常量值。
- Closure Compiler 调试难
- Packer 运行期消耗长
添加
defer
属性。仅对 IE 和 Firefox 有效。1
动态加载脚本。在需要的地方添加脚本链接。
1
2
3
4var script = document.createElement('script');
script.type = "text/javascript";
script.src = "default.js";
document.getElementByTagName('head')[0].appendChild(script)通过 XHR 对象下载文件。
引用 LazyLoad(1.5kb) 或 LABjs(4.5kb) 延迟加载插件。
1
2
3
4
5
6
7
8
9
10// [] 里可以放多个文件
LazyLoad.js(['default.js'], function(){
// doing something
});
// script() 下载对应的脚本文件;wait() 执行脚本文件后调用的函数;
// 如要多个脚本文件,写多个 script 即可,但多个时注意 script 和 wait 的执行顺序
$LAB.script('default')
.wait(function(){
// doing something
})HTML5 离线引用缓存
manifest
- 文件加上时间戳确保应用升级时资源最新
- CDN(内容分发网络)
- 避免双重求值
- eval(‘arr[0]’) 比 arr[0] 慢
- setTimeout() 、 setInterval() 第一个参数传递函数非字符串
- 避免重复工作
- 延迟加载(如判断浏览器,无需每次都判断,首次判断结果覆盖当前)
- 条件预加载(先判断后赋值到当前,适用于马上需要被加载的)
数据存储
数据分为:直接量(字符串、数字、布尔值、数组、对象、函数、正则表达式、null、undefined),变量,对象成员(以字符串为索引),数组元素(以数字为索引)
- 直接量与变量 比 对象成员与数组元素 要快,其中,局部变量 比 全局变量要快。因此定义一个对象、数组时直接赋值可以加快解析速度。
- 点表示法与括号表示法:在 Safari 中点表示法更快,其他浏览器无太大差别。
- with 和 try-catch:改变运行期上下文作用域链,从而改变不同变量的访问速度。
DOM 渲染
- 减少访问 DOM 的次数
- innerHTML 与 原生 DOM(createElement)
- webkit 内核浏览器中原生DOM快
- IE6 中,innerHTML快
- 访问集合元素(针对循环体里集合遍历次数多)
- 在循环体外存储集合长度
- 在循环体外拷贝集合元素为数组
- 把集合元素事先定义为局部变量
重排(reflow)和重绘(repaint)
- 产生原因有以下可能性:
添加或者删除 DOM
改变元素位置、大小、内容
改变浏览器窗口大小
渲染器初始化 尽可能减少重排或重绘次数
- 改变元素时,尽可能所有改变一次性处理,因为如果在改变的过程中再执行查询就会更新当前的所有数据,导致重排或重绘;或者为这些改变合并为一个类,通过控制类名达到改变效果。
隐藏元素->修改元素->重现元素
1
2
3el.style.display('none');
update();
el.style.display('block');创建文档片段(createDocumentFragment)
1
2
3var fragment = document.createDocumentFragment();
update();
el.appendChild(fragment);复制当前节点->修改元素->新节点替换旧节点
1
2
3
4// cloneNode 参数:true 复制自身以及递归复制当前节点的所有子孙节点;false 只复制当前节点
var clone = el.cloneNode(true);
update();
old.parentNode.replaceChild(clone, old);offset、scroll、computed 减少对这三个相关的获取次数
- 对于动画布局为脱离文档流。
- 事件委托(*)
- 产生原因有以下可能性:
算法
- 减少迭代工作量(倒序循环)
减少迭代次数(达夫设备:每次迭代最多赋值 8 个值,使用while 或 switch 来实现)
1
2
3
4// 执行 n 次,每次调用 8 次函数
var n = Math.floor(arr.length/8);
// 执行 1 次 调用 m 次函数
var m = arr.length%8;if-else
- 最有可能成立的条件放在首位
- 减少判断次数,条件间可以嵌套 if(…){ if(…){} }
- 运行速度:查找表 > switch > if-else
- 合并排序
- 迭代取代递归
- Memoization,适用于递归场景,把计算中的结果保存下来,比如计算 6!, 在函数计算体里,保存 5!、4!、3!…的计算结果。
- 位操作
- 与& 都为 1 的时候为 true
- 或| 只要有 1 个 1 都为 true
- 异或^ 只有 1 个 1 时为 true
- 取反~ 遇 0 即为 true
- 左移<<
- 右移>>
- 使用原生方法
Math.PI
Math.abs()
等
正则表达式
正则匹配步骤:
1、创建一个正则对象
2、找到匹配起始位置
3、匹配每个字元,如果失败了,回到起始位置重新尝试其他的可能性
4、匹配一轮失败,回到起始位置,从下一个字符开始作为起始位置继续匹配,直到所有的字符都匹配一次,或者匹配到正确的,才会结束。
- IE8 以前,+= 比 = … + … 快
- IE7 以前,数组项合并(join)更快
响应接口和 AJAX
- JS 文件执行不应超过 100ms
- 定时器延时建议最小值为 25ms
- start = +new Date() 加号 + 可以将 Date 对象转换为数字
- Web Workers
- 请求数据方式:
- XMLHTTPRequest(XHR)
- Dynamic script tag insertion 动态脚本标签插入。不能通过请求发送信息头,参数只能通过 GET。
- iframes(少用)
- Comet(少用)
- Multipart XHR(MXHR) 使用流功能:当 readyState 为 3 时进行操作,可以减少请求数,但不能缓存接收到的响应。
- Beacons 信标。适用于不需要或者需少量回馈时,发送大量数据到服务器
- 数据格式
- JSON 轻量级、解析快。解析 json 方式:eval(‘(‘ + text + ‘)’),SON.parse(text)
- JSONP 当涉及敏感数据时,应避用JSONP。
- 字符分隔自定义格式 十分轻量、解析大数据非常快,但服务端要构造,客户端也要解析这种构造。
- XML 广泛应用、支持良好。笨重、解析缓慢。
其他工具
- Apache Ant:基于 Java 的一个自动构建软件的工具
- Rake:基于 Ruby
- make:基于 Shell,针对 unix 固定系统
- 预处理 js 文件(cpp)
- YUI Profiler:性能分析工具
各浏览器渲染引擎
- | DOM渲染 | JS |
---|---|---|
Chrome | Webkit-WebCore | V8 |
Safari | Webkit-WebCore | SquirrelFish |
Firefox | Gecko | JagerMonkey |