代码改变世界

加快javascript代码运行 —— 避免长时间运行

2013-05-23 19:38  MoltBoy  阅读(2994)  评论(3编辑  收藏  举报

  前面一篇讨论了XHR对象已经异步的部分观点,异步能解决许多长时间运行交互问题,但它绝不是适用于任何地方。

  长时间运行的原因

  javascript运行在浏览器环境中,因而分配的资源数量是十分有限的,不同于桌面程序能随意控制它们想要的内存大小和CPU时间,Javascript被严格限制了。甚至长时间运行脚本都有一定约束,假如代码运行超过了特定的时间限制或者运行的语句数量超出特定约束,浏览器就会弹出错误提示框,询问是继续运行还是停止它。

  出现这种情况的原因主要有两个:过长或过深嵌套的函数调用;进行了大量的迭代处理。

for(var i = 0, len = obj.length; i < len; i++){
     process(obj[i]);          
}    

  上面代码为最常见的迭代情况,迭代次数未定,同时所有的迭代都是同步阻塞进行,也即是前一次迭代处理完成之后才能进行下一次迭代。

  • 该迭代是否必须同步进行呢?假如数据的处理会造成其他代码运行的阻塞,那么答案是:必须同步进行。
  • 迭代是否有必须按特定的顺序呢?通常情况下,迭代的数据集合没有固定的顺序,也就是说打乱顺序代码的运行。

  分块处理

  当你遇到某个迭代占用了大量时间,并且上述两点都是非必须,那么你可以尝试分割这个循环,也即是数组分块,小块小块地处理数组。基本的思路是:为要处理的项目建立一个queue,然后使用setTimeout取出下一个要处理的数据进行处理,接着另外设置一个setTimeout进行迭代。

function chunk(array, process, context){
    setTimeout(function(){
           var item = array.shift();     //取出下一个数据
           process.call(context, item);
           !!array.length && setTimeout(arguments.callee, 100); 
     }, 100);                            
}

  array为要处理的数据集合,process为处理数据的函数,context为执行的上下文。时间间隔设置为100ms,使得javascript进程有时间在处理数据的事件之间转入空闲,当然你可以根据你的需要修改这个间隔时间。

var data = [222,333,234,342,4536,5674,5634,2342,342,643,344,234,342,4536,5674,5634,2342,234,342,4536,5674,5634,2342];
function print(item){
    var div = document.getElementById('div');
    div.innerHTML += item + "<br />";
}
function chunk(arr, process, context){
    setTimeout(function(){
        var item = arr.shift();
        process.call(context, item);
                
        !!arr.length && setTimeout(arguments.callee, 100);
    }, 100);
}
chunk(data.concat(), print);

  注意,最后调用的时候,传递的是队列数组的副本,因为shift方法会改变原数组,当然要是不在意原数组的改变,就可以忽略这点。

  优势

  数组分块让繁琐的项目分拆成多个小块,按队列分开执行,在每个小块处理之后,给予浏览器处理其他的机会,这样可以避免长时间运行脚本的错误出现。当某个函数运行时间超过200ms以上,就可以考虑使用分块方式进行处理。

  其他

  另外,浏览器中某些DOM操作非常的频繁,若是连续过多的变化容易引起浏览器挂起,例如:resize事件,当浏览器发生改变,onresize事件将会被连续触发,导致时间频繁被调用。这样的情况,可以使用分块升级版。也就是将连续的同步代码拆分,让执行函数的请求停止一段时间之后才执行。

function throttle(method, context){
    clearTimeout(method.tId);
    method.tId = setTimeout(function(){
        method.call(context);
    }, 100);
}

  定时器的ID储存在方法的属性tId之中,每次调用都会先清除前一次的定时器ID,以阻止之前的调用被执行。时间间隔设置为100ms,也就是,100ms之内,不管被调用了多少次,但只执行一次,之前的多余次数都被清除。

window.onresize = function(){throttle(resizeFunc);};

参考链接:http://www.nczonline.net/blog/2009/06/05/speed-up-your-javascript-the-talk/