下面来看,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建议创建一个单独的重复定时器,每次执行多个操作。
原文地址: http://www.yiiyaa.net/1223
【编辑推荐】