第一章:加载和执行
-
脚本位置:
脚本会阻塞页面其他资源的下载,因此推荐所有< script >标签放到标签的底部 -
组织脚本:
1.不要把内嵌脚本放在< link >标签后,会导致页面阻塞去等待样式表的下载
2.把多个JS文件合并成一个,只需引用一个< script >标签,减少性能消耗 -
无阻塞的脚本:
1.defer属性:
指明本元素所含的脚本不会修改DOM,代码能安全的延迟执行。带有此属性的script标签可放在任何位置,直到DOM加载完成后才会执行。2.动态脚本加载:
function loadScript(url, callback){
var script = document.createElement("script");
script.type = "text/javascript";
if(script.readyState){ //IE浏览器
script.onreadystatechange = function(){
if(script.readyState == "loaded" || script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
}else{ //其他浏览器
script.onload = function(){
callback();
};
}
script.src = url;
document.head.appendChild(script);
}
3.XMLHttpRequest脚本注入:
优点:下载js代码但不立即执行;相同代码在所有主流浏览器中都可使用
缺点:js文件必须与所请求的页面处于相同的域
var xhr = new XMLHttpRequest();
xhr.open("get", "index.js", true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){
var script = document.createElement("script");
script.type = "text/javascript";
script.text = xhr.responseText;
document.body.appendChild(script);
}
}
};
xhr.send(null);
第二章:数据存取
-
四种数据存储位置:字面量(字符串/数字/布尔值/对象/数组/函数/正则表达式/null/undefined)、局部变量、数组元素、对象成员
-
字面量和局部变量的访问速度快于数组项和对象成员的访问速度(Firefox3优化了数组项)
-
由于局部变量存在于作用域链的起始位置,因此访问局部变量比访问跨作用域变量更快。变量在作用域链中的位置越深,访问所需的时间就越长。由于全局变量总处在作用域链的最末端,因此访问速度也是最慢的。
-
尽量使用局部变量,如果某个跨作用域的值在函数中被引用一次以上,那么就把它存储到局部变量里
var doc = document;
-
with语句可改变作用域链,虽避免书写重复代码,但令所有局部变量处于第二个作用域链对象中,访问代价更高,应避免使用。try-catch中的catch字句也具有同样的效果。
-
对象成员嵌套得越深,读取速度就越慢。每遇到点操作符,嵌套成员会导致js引擎搜索所有对象成员。
-
属性或方法在原型链中的位置越深,访问它的速度也越慢
-
把常用的对象成员、数组元素、跨域变量保存在局部变量中可改善JavaScript性能
第三章:DOM编程
-
文档对象模型(DOM)是一个独立于语言的,用于操作XML和HTML文档的程序接口(API)
-
DOM和JavaScript类似两个岛屿,每通过一次都要缴纳“过桥费”。应减少访问DOM的次数,把运算尽量留在JavaScript端处理。
-
对于任何类型的DOM访问,需要多次访问一个DOM属性或方法时,最好使用一个局部变量缓存此成员。
-
使用速度更快的API,比如querySelectorAll()和querySelector()–获取第一个匹配的节点,firstElementChild等等
-
把集合的长度缓存到一个变量中,并在迭代中使用它。如果需要经常操作集合,建议把它拷贝到一个数组中
-
重排:浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。重绘:完成重排后,浏览器会重新绘制受影响的部分到屏幕中。批量修改样式时,“离线”操作DOM树,使用缓存,并减少访问布局信息的次数。
-
动画中使用绝对定位,使用拖放代理,避免重排
-
使用事件委托来减少事件处理器的数量
第四章:算法和流程控制
- 在for循环中定义一个新变量相当于在循环体外定义一个新变量
- for-in循环可以枚举任何对象的属性名。不要用来遍历数组成员
- for循环、while循环、do-while循环、for-in循环与性能无关,应该减少每次迭代处理的事务以及迭代的次数
- 在JavaScript中,倒序循环会略微提升性能,前提是你排除那些额外操作带来的影响。
- 当循环复杂度为O(n)时,减少每次迭代的工作量最有效,当复杂度大于O(n),建议着重减少迭代次数(达夫设备)
- forEach()遍历一个数组的所有成员,并在每个成员上执行一个函数。基于循环的迭代比基于函数的迭代快8倍。
- switch比if-else快,但并不总是最佳解决方案。在判断条件较多时,使用查找表比switch和if-else更快
- 浏览器的调用栈大小限制了递归算法在JavaScript中的应用;栈溢出错误会导致其他代码中断运行。此时可把方法改为迭代算法,或使用Memoization来避免重复计算
第五章:字符串和正则表达式
-
当连接数量巨大或尺寸巨大的字符串时,数组合并是在IE7及其更早版本中唯一性能合理的地方,推荐
str += "one" + "two";
-
如果不需考虑IE7及其更早版本的性能,数组项合并是最慢的字符串连接方法之一,推荐
str = str + "one" + "two";
赋值表达式由str开始作为基础,每次给它附加一个字符串,由左向右依次连接,避免了使用临时字符串 -
Array.prototype.join方法
var str = "I'm a thirty-five character string", strs = [], newStr, appends = 5000; while(appends--){ strs[strs.length] = str; } newStr = strs.join("");
-
String.prototype.concat方法:最灵活的字符串合并方法,可附加1-n个字符串或数组中的所有字符串。但比简单的+和+=稍慢。
-
正则表达式工作原理:编译 --> 设置起始位置 --> 匹配每个正则表达式字元 --> 匹配成功或失败
-
回溯是正则表达式匹配功能的基本组成部分也是正则表达式的低效之源。避免回溯:使相邻的字元互斥,避免嵌套量词对同一字符串的相同部分多次匹配,使用预查和反向引用的模拟原子组(?>…)
-
搜索字面字符串时不需要使用正则表达式
-
去除字符串首尾空白
String.prototype.trim = function() {
var str = this.replace(/^\s+/, ""),
end = str.length - 1,
ws = /\s/;
while (ws.test(str.charAt(end))) {
end--;
}
return str.slice(0, end + 1);
}
第六章:快速响应的用户界面
-
浏览器UI线程:用于执行JavaScript和更新用户界面的进程。其工作基于一个简单的队列系统,执行的任务是运行JavaScript代码、UI更新,包括重绘重排。每一次输入可能会导致一个或多个任务被加入队列。
-
浏览器限制JavaScript任务的运行时间:调用栈大小限制和长时间运行脚本时间限制(长时间运行脚本定时器 / 失控脚本定时器)。第一种记录脚本执行语句的数量;第二种记录脚本执行的总时长。
-
单个JavaScript操作花费的时间不应该超过100毫秒
-
setTimeout()创建一个只执行一次的定时器,setInterval()创建一个周期性重复运行的定时器。时间参数表示任务何时被添加到UI队列,此任务会等队列中其他所有任务执行完毕才会执行。
-
如果UI队列中已经存在由同一个setInterval()创建的任务,那么后续任务不会被添加到UI队列中。定时器通常不精准,相差大约几毫秒。
-
使用定时器处理数组
function processArray(items, process, callback){ var todo = items.concat(); setTimeout(function(){ process(todo.shift()); if (todo.length > 0){ setTimeout(arguments.callee, 25); }else{ callback(items); } }, 25); }
-
分割任务(本质上是数组处理模式)
function multistep(steps, srgs, callback){
var tasks = steps.concat(); //克隆数组
setTimeout(function(){
//执行下一个任务
var task = tasks.shift();
task.apply(null, args || []);
//检查是否有其他任务
if (tasks.length > 0){
setTimeout(arguments.callee, 25);
}else{
callback();
}
}, 25);
}
-
同一时间只有一个定时器存在,只有当这个定时器结束时才会创建一个。
-
每个Web Worker都有属于自己的全局运行环境,不仅不会影响浏览器UI,也不会影响其他Worker中运行的代码。
-
postMessage()方法给Worker传递数据; onmessage事件来接收数据; ImportScripts()方法加载外部JavaScript文件
-
Web Workers适用于处理纯数据,或者与浏览器UI无关的长时间运行脚本。
第七章:Ajax
-
Ajax通过延迟下载体积较大的资源文件来使页面加载速度更快,异步传输数据,甚至可以只用一个Http请求就获取整个页面的资源。
-
XMLHttpRequest
是目前最常用的向服务器请求数据的技术,允许异步发送和接收数据。readyState=4说明整个响应已接收完毕;readyState=3说明正在与服务器交互;这就是所谓的“流”(streaming)
不能使用XHR从外域请求数据,且低版本IE不支持“流”也不提供readyState为3的状态,从服务器传回的数据被当做字符串或者XML对象,意味着处理大量数据会很慢。
GET请求:不会改变服务器状态,只会获取数据(幂等行为);
POST请求:当请求的URL加上参数的长度接近或超过2048个字符时 -
动态脚本注入
克服了XHR的最大限制:它能跨域请求数据。但不能设置请求的头信息,参数传递只能GET方式 -
multipart XHR
允许客户端只用一个HTTP请求就可以从服务器端向客户端传送多个资源。在服务端将资源打包成一个由双方约定的字符串分割的长字符串并发送到客户端。然后用JavaScript代码处理这个长字符串,并根据它的mime-type类型和传入的其他“头信息”解析出每个资源 -
发送数据
XHR同样能用于把数据传回服务器,数据可以用POST和GET方式传回包括任意数量的HTTP头信息。GET方式会更快,因为一个GET请求往服务器只发送一个数据包,而一个POST请求至少发送两个数据包,一个装载头信息,一个装载POST正文。
信标(Beacons),使用JavaScript创建一个新的Image对象,并把src属性设置为服务器上脚本的URL。该URL包含我们要通过GET传回的键值对数据,以及并没有创建img元素或把他插入DOM。 -
数据格式
XML:冗长,性能数据较慢,解析很麻烦JSON: 是一种使用JavaScript对象和数组直接编写的轻量级且易于解析的数据格式。可以简单的使用eval()来解析JSON字符串
JSON-P(JSON填充):在使用动态脚本注入时,JSON数据被当成另一个Javascript文件并作为原生代码执行。为实现这一点,这些数据必须封装在一个回调函数里。不要把任何敏感数据编码在JSON-P中,因为你无法确认它是否保持私有状态。
HTML:传输数据量明显偏大,还需要较长的时间来解析,作为一种数据格式,它既缓慢又臃肿。
自定义格式:可以很快速的下载,且易于解析,只需简单的调用字符串的split()并传入分隔符作为参数即可。创建自定义格式时,最重要的决定之一就是采用哪种分隔符。
-
最快的Ajax请求就是没有请求,避免发送不必要的请求:设置HTTP头信息;把获取到的数据存储到本地避免再次请求。
第八章:编程实践
-
避免双重求值
eval()、Function()构造函数、setTimeout()和setInterval()都允许传入一个JavaScript代码字符串并执行它。当在JavaScript代码中执行另一段JavaScript代码时,会导致双重求值的性能消耗。避免使用eval()、Function()构造函数;setTimeout()和setInterval()第一个参数建议传入函数而不是字符串。
-
使用Object/Array直接量是创建对象和数组最快的方式
-
避免重复工作:别做无关紧要的工作,别重复做已经完成的工作。可使用延迟加载或条件预加载
-
在进行数学计算时,考虑使用直接操作数字的二进制形式的位运算。
-
尽量使用JavaScript的原生方法,特别是数学运算和DOM操作。
第九章:构建并部署高性能JavaScript应用
本章使用到的技术可能已经过时,主要理解其思想。
- Apache Ant是一个软件构建自动化工具,用Java实现并用XML描述构建过程
- 合并JavaScript文件以减少HTTP请求数
- JavaScript压缩指的是把JavaScript文件中与运行无关的部分剥离,注释、不必要的空白字符。
- 使用YUI Compressor压缩JavaScript文件。将局部变量替换成更短形式;将方括号表示法替换成点表示法;替换转义符号;合并常量;
- JavaScript的HTTP压缩:HTTP请求头中Accept-Encoding可以用来压缩文档,以得到更快的下载,值包括gzip、compress、deflate和identity。
- 通过正确设置HTTP响应头来缓存JavaScript文件,通过向文件名增加时间戳来避免缓存问题
- 使用CDN提供JavaScript文件;CDN不仅提升性能,还管理文件的压缩和缓存。