在Web开发的时候常常会遇到浏览器不响应事件进入假死状态,甚至弹出“脚本运行时间过长“的提示框,若是出现这种状况说明你的脚本已经失控了,必须进行优化。javascript
为何会出现这种状况呢,咱们先来看一下浏览器的内核处理方式:java
了解了浏览器的内核处理方式就不难理解浏览器为何会进入假死状态了,当一段JS脚本长时间占用着处理机就会挂起浏览器的GUI更新,然后面的事件响应也被排在队列中得不处处理,从而形成了浏览器被锁定进入假死状态。另外JS脚本中进行了DOM操做,一旦JS调用结束就会立刻进行一次GUI渲染,而后才开始执行下一个任务,因此JS中大量的DOM操做也会致使事件响应缓慢甚至真正卡死浏览器,如在IE6下一次插入大量的HTML。而若是真的弹出了“脚本运行时间过长“的提示框则说明你的JS脚本确定有死循环或者进行过深的递归操做了。算法
Nicholas C. Zakas认为不论什么脚本,在任什么时候间、任何浏览器上执行都不该该超过100毫秒,不然必定要将脚本分解成若干更小的代码段。那么咱们该如何来作呢:shell
第一步,优化你的循环,循环体中包含太多的操做和循环的次数过多都会致使循环执行时间过长,并直接致使锁死浏览器。若是循环以后没有其余操做,每次循环只处理一个数值,并且不依赖于上一次循环的结果则能够对循环进行拆解,看下面的chunk的函数:数组
function
chunk(array, process, context) {
浏览器
缓存setTimeout(
function
() {
多线程
var
item = array.shift();
闭包 process.call(context, item);
异步
if
(array.length > 0) {
setTimeout(arguments.callee, 100);
}), 100);
}
chunk()函数的用途就是将一个数组分红小块处理,它接受三个参数:要处理的数组,处理函数以及可选的上下文环境。每次函数都会将数组中第一个对象取出交给process函数处理,若是数组中还有对象没有被处理则启动下一个timer,直到数组处理完。这样可保证脚本不会长时间占用处理机,使浏览器出一个高响应的流畅状态。
其实在我看来,借助JS强大的闭包机制任何循环都是可拆分的,下面的版本增长了callback机制,使可再循环处理完毕以后进行其余的操做。
function
chunk(array,process,cbfun){
var
i=0,len = array.length;
//这里要注意在执行过程当中数组最好是不变的
setTimeout(
function
(){
process( array[i] , i++ );
//循环体要作的操做
if
( i < len ){
setTimeout(arguments.callee,100);
}
else
{
cbfun()
//循环结束以后要作的操做
}
}
}
第二步,优化你的函数,若是函数体内有太多不相干但又要一块儿执行的操做则能够进行拆分,考虑下面的函数:
function
dosomething(){
dosomething1();
dosomething2();
}
dosomething1和dosomething2互不相干,执行没有前后次序,可用前面提到的chunk函数进行拆分:
function
dosomething(){
chunk([dosomething1,dosomething2],
function
(item){item();})
}
或者直接交给浏览器去调度
function
dosome(){
setTimeout(dosomething1,0);
setTimeout(dosomething2,0);
}
第三步,优化递归操做,函数递归虽然简单直接可是过深的递归操做不但影响性能并且稍不注意就会致使浏览器弹出脚本失控对话框,必须当心处理。
看如下斐波那契数列的递归算法:
function
fibonacci(n) {
return
n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);
};
fibonacci(40)这条语句将重复调用自身331160280次,在浏览器中执行必然致使脚本失控,而采用下面的算法则只须要调用40次
fibonacci =
function
(n){
var
memo = {0:0,1:0};
//计算结果缓存
var
shell =
function
(n){
var
result = memo[n];
if
(
typeof
result !=
'number'
)
//若是值没有被计算则进行计算
memo[n] = shell(n-1) + shell(n -2);
return
memo[n];
}
return
shell(n);
}
这项技术被称为memoization,他的原理很简单就是一样的结果你不必计算两次。另外一种消除递归的办法就是利用迭代,递归和迭代常常会被做为互相弥补的方法。
第四步,减小DOM操做,DOM操做的代价是至关昂贵的,大多数DOM操做都会触发浏览器的回流(reflow)操做。例如添加删除节点,修改元素样式,获取须要通过计算的元素样式等。咱们要作的就是尽可能少的触发回流操做。
el.style.width =
'300px'
el.style.height =
'300px'
el.style.backgroundColor =
'red'
上面的操做会触发浏览器的三次回流操做,再看下面的方式:
el.className =
'newStyle'
经过设置改元素的className一次设置多个样式属性,将样式写再CSS文件中,只触发一次回流,达到了一样是效果并且效率更高。由于浏览器最擅长的就是根据class设置样式。
还有不少能够减小DOM操做的方法,在此就很少说了,可是一个基本的原则就是让浏览器去作它本身擅长的事情,例如经过class来改变元素的属性。
相信通过上面的优化的过程一定能够大大提升用户体验,不会出现浏览器被锁死和弹出脚本失控的对话框,使你的浏览器从繁重的任务中解放出来。须要指出的是上面这些优化并非必须的,只有当一段脚本的执行时间真的影响到了用户体验才须要进行。虽然它们让用户以为脚本的执行变快了,但其实完成同一个操做的时间可能被延长了,这些技术只是让浏览器处于一个快速响应的状态,使用户浏览更流畅。
最后送一句忠告:过早优化是万恶之源。