JavaScript中分解任务

我们通常将一个任务分解成一系列子任务。如果一个函数运行时间太长,那么查看它是否可以分解成一系列能够短时间完成的较小的函数。可将一行代码简单地看作一个原子任务,多行代码组合在一起构成一个独立任务。某些函数可基于函数调用进行拆分。例如:
      function saveDocument(id){
            openDocument(id)
            writeText(id);
            closeDocument(id);
            updateUI(id);
      }
      如果函数运行时间太长,它可以拆分成一系列更小的步骤,把独立方法放在定时器中调用。你可以将每个函数都放入一个数组,然后使用前一节中提到的数组处理模式:

      function saveDocument(id){
            var tasks = [openDocument, writeText, closeDocument, updateUI];
            setTimeout(function(){
                  var task = tasks.shift();
                  task(id);
                  if (tasks.length > 0){
                        setTimeout(arguments.callee, 25);
                  }
            }, 25);
      }
      这个版本将每个方法放入任务数组,然后在每个定时器中调用一个方法。从根本上说,现在它成为数组处理模式,只有一点不同:处理函数就包含在数组项中。正如前面一节所讨论的,此模式也可封装重用:
      function multistep(steps, args, 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);
      }
      multistep()函数接收三个参数:用于执行的函数数组,为每个函数提供参数的参数数组,当处理结束时调用的回调函数。函数用法如下:
      function saveDocument(id){
            var tasks = [openDocument, writeText, closeDocument, updateUI];
            multistep(tasks, [id], function(){
                  alert("Save completed!");
            });
      }
      注意传给multistep()的第二个参数必须是数组,它创建时只包含一个id。正如数组处理那样,使用此函数的前提条件是:任务可以异步处理而不影响用户体验或导致依赖代码出错。
      (一)限时运行代码
      有时每次只执行一个任务效率不高。考虑这样一种情况:处理一个拥有1'000个项的数组,每处理一个项需要1毫秒。如果每个定时器中处理一个项,在两次处理之间间隔25毫秒,那么处理此数组的总时间是(25 + 1) × 1'000 = 26'000 秒,也就是26 秒。如果每批处理50个,每批之间间隔25毫秒会怎么样呢?整个处理过程变成(1'000 / 50) × 25 + 1'000 = 1'500毫秒,也就是1.5秒,而且用户也不会察觉界面阻塞,因为最长的脚本运行只持续了50毫秒。通常批量处理比每次处理一个更快。
      如果你记住JavaScript可连续运行的最大时间是100毫秒,那么你可以优化先前的模式。我的建议是将这个数字削减一半,不要让任何JavaScript代码持续运行超过50毫秒,只是为了确保代码永远不会影响用户体验。
      可通过原生的Date 对象跟踪代码的运行时间。这是大多数JavaScript分析工具所采用的工作方式:
      var start = +new Date(),
      stop;
      someLongProcess();
      stop = +new Date();
      if(stop-start < 50){
            alert("Just about right.");
      } else {
            alert("Taking too long.");
      }
      由于每个新创建的Data 对象以当前系统时间初始化,你可以周期性地创建新Data对象并比较它们的值,以获取代码运行时间。加号(+)将Data 对象转换为一个数字,在后续的数学运算中就不必再转换了。这一技术也可用于优化以前的定时器模板。
      processArray()方法通过一个时间检测机制,可在每个定时器中执行多次处理:
      function timedProcessArray(items, process, callback){
            var todo = items.concat();
            setTimeout(function(){
                  var start = +new Date();
                  do {
                        process(todo.shift());
                  } while (todo.length > 0 && (+new Date() – start < 50));
                  if (todo.length > 0){
                        setTimeout(arguments.callee, 25);
                  } else {
                        callback(items);
                  }
            }, 25);
      }
      此函数中添加了一个do-while循环,它在每个数组项处理之后检测时间。定时器函数运行时数组中存放了至少一个项,所以后测试循环比前测试更合理。在Firefox 3中,如果process()是一个空函数,处理一个1'000个项的数组需要38 – 34毫秒;原始的processArray()函数处理同一个数组需要超过25'000毫秒。这就是定时任务的作用,避免将任务分解成过于碎小的片断。
      (二)定时器与性能
      定时器使你的JavaScript代码整体性能表现出巨大差异,但过度使用它们会对性能产生负面影响。使用定时器序列,同一时间只有一个定时器存在,只有当这个定时器结束时才创建一个新的定时器。以这种方式使用定时器不会带来性能问题。
      当多个重复的定时器被同时创建会产生性能问题。因为只有一个UI线程,所有定时器竞争运行时间。Google Mobile的Neil Thomas 将此问题作为测量性能的方法进行研究,针对iPhone和Android上运行的移动Gmail程序。
      Thomas发现低频率的重复定时器——间隔在1 秒或1 秒以上——几乎不影响整个网页应用的响应。这种情况下定时器延迟远超过使UI 线程产生瓶颈的值,因此可安全地重复使用。当多个重复定时器使用更高的频率(间隔在100到200毫秒之间),Thomas发现移动Gmail程序明显变慢,反应较差。Thomas研究的言外之意是,要在你的网页应用中限制高频率重复定时器的数量。同时,Thomas建议创建一个单独的重复定时器,每次执行多个操作。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值