高性能javascript笔记
----------------------------------------------------------- 第一章 加载和执行 -------------------------------------
1.脚本位置
浏览器在遇到<script>标签时会等待脚本下载完并执行完才会继续渲染页面
因为js代码有可能会改变dom结构,所以需要等待js的执行完成
遇到<script>标签 -> 下载脚本 -> 执行脚本 -> 继续渲染
旧浏览器:逐个下载,逐个执行
新浏览器:并发下载,顺序逐个执行
so: 脚本尽量放在靠近</body>的底部
尽可能合并脚本(下载一个比下载多个快)
2.延迟的脚本
defer:立刻下载,等到onload事件被触发前才执行(仅IE4.0+和Firefox3.5+支持)
动态创建<script>节点:添加到页面时开始下载,下载完成立刻执行
* 下载完成的事件:readystatechange(IE)、onload(其他浏览器)
XHR脚本注入:XMLHttpRequest来获取js文件内容,再动态创建<script>节点
* 必须处于相同域,不适用大型Web应用
3.推荐的方式
先添加动态加载所需的代码,然后加载其他代码:
<script type="text/javascript" src = "loader.js"></script>
<script type="text/javascript">
loadScript("the-rest.js"),function(){
Application.init();
}
</script>
放置</body>闭合标签前,保证DOM结构已经创建完毕,无需其他的时间,例如window.onload来检测页面是否准备好
4.类库
LazyLoad.js:
<script type="text/javascript" src = "lazyload-min.js"></script>
<script type="text/javascript">
LazyLoad.js(["first.js","second.js"]),function(){
Application.init();
}
</script>
lab.js:
<script type="text/javascript" src = "lab.js"></script>
<script type="text/javascript">
$LAB.script("first.js").wait() // 如果需要按序执行则需要加上wait()
.script("second.js")
.wait(function(){
Application.init();
});
</script>
--------------------------------第二章 数据访问---------------------------------------
1.作用域链和标识符解析
内部属性[[Scope]] --- 作用域链 --- 0 --- 活动对象([[Scope]]属性中所包含的对象,即函数范围内的变量)
--- 1 --- 全局对象(this, window, document 等等全局变量)
2.so 尽量把链深处的变量存储到局部变量里,介以提升性能
* 采用优化过的js引擎不存在这种问题,老版的IE、Firefox和Safari都有问题
3.改变作用域链:
with(document){...} //with把document推进了作用域链的第一层,但是导致访问局部变量路径变远了,所以不推荐
try{}catch(ex){} //catch里面把异常对象推入了作用域链的头部
4.访问对象也一样,首先在实例中查找,然后再去原型链中查找
so 如果需要访问多次,那么缓存对象属性可以提升性能
--------------------------------第三章 DOM编程---------------------------------------
1.尽量少访问DOM,把运算留在ECMAScript这一端处理
2.innerHTML属性和类似document.createElement()、document.createTextNode()的原生DOM方法性能差不多
3.cloneNode()比createElement要稍快一点
4.HTML集合
document.getElementsByName();
document.getElementsByClassName();
document.getElementsByTagName();
HTML集合是动态的,类数组,提供一个length属性
* 访问length属性时会重新执行一次查询的过程
so,缓存length属性很有必要
5.只返回元素节点
children childNodes
childElementCount childNodes.length
firstElementChild firstChild
lastElementChild lastChild
nextElementSibling nextSibling
previousElementSibling previousSibling
6.选择器API
原生DOM方法:querySelectorAll();
7.重绘与重排
构建DOM树与渲染树 --- 绘制页面元素
重排:
添加、删除DOM元素
元素位置改变
元素尺寸改变
内容改变(例如文本改变、图片被另外一个不同尺寸的图片替代)
页面渲染器初始化
浏览器窗口尺寸改变
浏览器通过队列化修改并批量执行来优化重排过程,但以下属性会强制刷新队列:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scroll...
client...
getComputedStyle()(currentStyle in IE)
以上属性需要返回最新的布局信息,所以浏览器需要执行队列中的"待处理变化"并触发重排以返回正确的值
so 以上属性尽量少使用,并且使用这些属性的位置应该在修改布局信息的后面,中间的话会导致多次重排
8.最小化重绘和重排
·合并多次所有的改变一次处理
·批量修改DOM
脱离文档流
应用多重改变
带回文档
三种方式:隐藏元素 --- 修改 --- 重新显示
使用文档片段(document fragment)
拷贝 --- 修改拷贝 --- 替换原始(replaceChild(new, old))
9.缓存布局信息
10.让元素脱离动画流
绝对定位页面上的动画元素,将其脱离文档流
让元素动起来,只重绘了该元素,会临时覆盖部分页面
动画结束时恢复定位,只下移一次文档的其他元素
11.元素很多时应避免使用:hover这个CSS伪选择器
12.事件委托
只绑定最外围的元素点击事件,判断来源是否是目标
--------------------------------第四章 算法和流程控制---------------------------------------
1.for-in循环的性能只有其他循环的1/7,
so 尽量不要使用for-in来遍历对象的属性名
2.少量条件用if-else, 大量条件用switch-case
3.列表查找比循环查找要快
so, 数据放置一个Array中, 通过位置来查找
4.尽量减少循环
5.存在重复的计算结果时,可以使用缓存
--------------------------------第五章 字符串和正则表达式---------------------------------------
1.字符串连接
把基础字符串及放置左边可以提升性能
* 因为除IE外,其他浏览器会尝试为左侧的字符串分配更多的内存,然后简单地将第二个字符串拷贝至它的末尾
2.Array.prototype.join();
String.prototype.concat(); //concat比普通的+和+=以及join慢一点
3.正则表达式的编译很快,只需要注意别在循环中重复编译正则表达式就行
while(/regex1/.test(str1))
4.只是检测位置不适用正则表达式
例如检测是否以;结尾:
/;$/ --- str.charAt(str.length-1)== ";";
其他函数有slice、substr、substring、indexOf和lastIndexOf
5.去除首尾空格
用两个子表达式综合效率要高一些,尤其是在处理长字符串时
str.replace(/^\s+/,"").replace(/\s+$/,"");
* 其他有一次性处理完的,但多少有各方面的性能问题
比方说/^\s+|\s+$/,每个字符串都会去匹配这两个分支条件
--------------------------------第六章 快速响应的用户界面---------------------------------------
1.js的执行不应超过100毫秒(用户体验中能忍受的页面阻塞时间最大值)
2.定时器的推荐最小值为25毫秒
* windows系统的最小识别为15毫秒,设置一个小于15毫秒的定时值,IE会锁定
* 在小于10毫秒时,各浏览器各系统均会有不同的表现
3.可以通过定时器来依次执行多个任务
* 将一个任务分割成多个任务,用定时器来执行
4.Web Workers
Worker没有绑定UI线程,适用于纯数据处理,与网页代码通过事件接口进行通信
网页代码:
var worker = new Worker("code.js");
worker.onmessage = faunction(event){
// ...
}
worker.postMessage("Thyiad");
worker代码(code.js):
importScripts("file1.js", "file2.js");
self.onmessage = function(event){
self.postMessage("Hello, " + event.data + "!");
}
--------------------------------第七章 AJAX---------------------------------------
1.常用的三种技术是:XHR、动态脚本注入和multipart XHR
2.XHR
readyState的值
3 正在与服务器交互
4 整个响应已接收完毕,可进行操作
* GET常用来请求数据,POST则用来发送数据
一个GET请求只会发送一次数据包,而一个POST请求会发送两次数据包(一个装载头一个装载正文)
应该在参数接近或超过2048个字符时,才应该使用POST获取数据,因为IE限制URL长度
* 不能跨域请求数据
3.动态脚本注入
动态创建一个script元素,设置src属性为不同域的URL
* 返回的响应消息必须是可执行的JavaScript代码
4.Multipart XHR
一次请求多个资源,从readyState值为3时开始设定一个定时器监听处理数据(需要自己定义数据格式并处理)
* 这种方式资源不会被缓存
* IE6、7不支持readyState为3的状态和data:URL
5.Beancons - 信标
通过创建一个Image,设定src来回传数据
var beacon = new Image();
beacon.src=url+'?'+params.join('&');
beancon.onload = function(){ // 通过监听image的load事件来处理简单返回时间
if(this.width === 1){ // 如果不需要返回数据,那么响应中应该发送一个 204 No Content 状态码(即:不带消息正文)
} // 以阻止客户端继续等待永远不会到来的消息正文
else{}
}
6.数据格式
XML 不推荐,数据量大解析又慢
JSON 推荐,数据轻便解析又快
JSON-P 返回的文本作为js代码直接执行(用eval直接调用)
HTML 不推荐,既缓慢又臃肿
自定义 同JSON,适用的情景下速度还会比JSON更快点
7.缓存数据
用GET请求数据、响应中发送 Expires 头信息:Expires: Mon, 28 Jul 2015 23:30:00 GMT // 告诉浏览器缓存此响应到7月
本地数据存储:使用对象的属性存储缓存(键存url,值存返回数据)
* 本地存储最适用移动设备,大多移动设备的浏览器都很小或没有缓存
--------------------------------第八章 编程实践---------------------------------------
1.避免双重求值
尽量不使用eval和Function构造函数,以避免双重求值带来的性能消耗
同样的,应该给setTimeout()和setInterval()传入函数而不是字符串作为参数
2.使用直接量创建对象和数组 - 效率更高
3.不要重复工作
当需要检测浏览器时,可使用延迟加载或条件预加载
延迟加载:
function addHandler(target, eventType, handler){
if(target.addEventListener){
addHandler = function(target, eventType, handler){
target.addEventListener(eventType, handler, false);
}
}
else{
addHandler = function(target, eventType, handler){
target.attachEvent("on"+eventType, handler);
}
}
addHandler(target, eventType, handler);
}
条件预加载:
var addHandler = document.body.addEventListener ?
function(target, eventType, handler){
target.addEventListener(eventType, handler, false);
} :
function(target, eventType, handler){
target.attachEvent("on"+eventType, handler);
};
4.使用速度快的部分
位操作
toString()方法把数字转换为二进制形式的字符串:
var num = 25;
alert(num.toString(2)); //"11001"
是否为2的整数:
var num = 25;
if(num & 1){ // 奇数&1 => 1
}
else{} // 偶数
位掩码:
var OPTION_A = 1, OPTION_B = 2, OPTION_C = 4, OPTION_D = 8, OPTION_E = 16;
var options = OPTION_A | OPTION_C | OPTION_D;
if(options & OPTION_A){ //选项A是否在列表中
// ...
}
5.原生方法
尽量使用原生方法,比如数学计算(Math)和CSS选择器(querySelector()、querySelectorAll())
--------------------------------第九章 构建并部署高性能JavaScript应用---------------------------------------
1.Apache Ant
2.合并多个JavaScript文件
3.预处理JavaScript文件
在js代码中添加宏定义(#define, #undef)和条件编译(#if, #ifdef, #ifndef)
4.JavaScript压缩
JSMin http://www.crockford.com/javascript/jsmin.html
YUI Compressor http://developer.yahoo.com/yui/compressor
Closure Compiler http://code.google.com/closure/compiler/
Packer http://dean.edwards.name/packer/
5.JavaScript的HTTP压缩
Accept-Encoding HTTP头:值为gzip、compress、deflate和identity
服务器会选择最合适的编码方法,通过Content-Encoding HTTP头来告知浏览器
6.缓存JavaScript文件
* Expires HTTP 响应头
* 客户端存储机制(js自己控制)
* HTML5 离线应用缓存(manifest属性,mime type为text/cache-manifest)
7.处理缓存问题
推荐使用时间戳后缀
8.CDN
--------------------------------第十章 工具---------------------------------------
1.原生分析
new Date(); //通过Date相减
2.YUI Profiler
3.FireBug
4.Console API
profile()、profileEnd()
console.profile("regexTest");
regexTest();
console.profileEnd();
* profileEnd()会阻塞后续执行,所以可以将profileEnd()调用封装在setTimeout中
5.Page Speed
对如何重构进行分析建议,比如哪些脚本在load之前没有用到过,可以延迟加载
* FireBug插件
6.Fiddler
HTTP调试代理工具,整个网络过程的Timeline进行分析,哪块占用时间多需要优化
7.YSlow
分析后的优化建议工具
* FireBug插件
8.dynatrace Ajax Edition
同Fiddler的作用,可以监控整个过程的时间,更能深入到特定的事件