利用Function.apply()优化JavaScript数组操作与性能提升指南

作者:极速慢跑时间:2026-10-29 19:09:04浏览:216

本文深入解析JavaScript中Function.apply()的核心用法,通过Math.max求最大值和数组合并两个实例,展示其相比传统循环的优雅与高效。结合20万数据测试,apply方案性能提升约5倍。此外,文章还总结了循环优化、变量作用域、类型转换及DOM操作等提升JS脚本性能的关键技巧,帮助开发者编写更高效的代码。

Function.apply()核心用法与性能优势

接下来探讨如何利用Function.apply()来增强程序运行效率。

首先从Math.max()函数入手,该函数支持接收任意数量的参数,并返回其中的最大值。

例如:


alert(Math.max(5,8))  //8 
alert(Math.max(5,7,9,3,1,6))  //9 

然而,在实际开发中,我们往往需要获取数组内的最大元素。


var arr=[5,7,9,1] 
alert(Math.max(arr))  //  这样却是不行的。一定要这样写 
function getMax(arr){ 
  var arrLen=arr.length; 
  for(var i=0,ret=arr[0];i<arrLen;i++){ 
    ret=Math.max(ret,arr[i]);     
  } 
  return ret; 
}  

常规写法不仅繁琐且效率低下。若采用apply方法,参考以下代码:


function getMax2(arr){ 
  return Math.max.apply(null,arr) 
} 

两段代码实现相同功能,但getMax2版本更为简洁、优雅且高效。

以下是性能对比测试:

getMax性能测试


var myArr=new Array() 
function fillRnd(arrLen){ //填入 arrLen个1-10的随机数字到数组 
  for(var i=0,arr=[];i<arrLen;i++){ 
    arr[i]=Math.ceil(Math.random()*10) 
  } 
  return arr 
} 
function getMax(arr){ 
  var arrLen=arr.length; 
  for(var i=0,ret=arr[0];i<arrLen;i++){ 
    ret=Math.max(ret,arr[i]);     
  } 
  return ret; 
} 
function getMax2(arr){ 
  return Math.max.apply(null,arr) 
} 
myArr=fillRnd(20*10000) //生成20万个随机数填到数组 
var t1=new Date() 
var max1=getMax(myArr) 
var t2=new Date() 
var max2=getMax2(myArr) 
var t3=new Date() 
if (max1!==max2) alert("error") 
alert([t3-t2,t2-t1])  //在我机器上 96,464 .不同的机器,结果可能有差异 

经20万条数据测试,getMax2耗时96ms,而getMax耗时464ms,两者性能差距达5倍。

再看数组的push方法示例。


var arr1=[1,3,4]; 
var arr2=[3,4,5];

若需将arr2展开并逐一追加至arr1,最终使arr1=[1,3,4,3,4,5]。

直接执行arr1.push(arr2)无法达成目标,结果将变为[1,3,4, [3,4,5]]。

通常需借助循环逐个push(也可用arr1.concat(arr2),但concat不修改原数组)。


var arrLen=arr2.length 
for(var i=0;i<arrLen;i++){ 
  arr1.push(arr2[i]) 
}

引入Apply后,处理变得异常简便。


Array.prototype.push.apply(arr1,arr2) 

JavaScript脚本性能优化综合指南

附录:JavaScript脚本性能优化策略

随着网络带宽与硬件性能的提升,富客户端技术日益普及,Ajax成为主流。JavaScript作为解释型语言,其执行效率难以与C/Java等编译型语言媲美,限制了客户端处理能力。为改善性能,基于过往大量测试经验,本文分享优化JavaScript脚本的实用建议。

语言层面优化:循环与变量

语言层面优化

循环结构

循环是常用的控制结构,多数功能依赖其实现。JavaScript提供for(;;)、while()和for(in)三种循环。其中,for(in)因需查询散列键,效率极低,应尽量避免使用。for(;;)与while()在常规使用下性能基本等价。

然而,具体使用方式对性能影响显著。测试中发现有趣现象(见附录),结论如下:

若循环变量递增或递减,避免单独赋值,应在最后一次读取时使用嵌套的++或--操作符。

若需与数组长度比较,应预先将数组length属性存入局部变量,以减少查询次数。

局部与全局变量

局部变量访问速度优于全局变量,因为全局变量实为全局对象成员,而局部变量存储于函数栈中。

避免使用Eval

使用eval相当于在运行时重新调用解释引擎解析内容,耗时巨大。此时可利用JavaScript闭包实现函数模板(闭包详情参考函数式编程相关内容)。

减少对象查找

由于JavaScript的解释特性,a.b.c.d.e需至少4次查询:先查a,再查a中的b,接着查b中的c,依此类推。若此类表达式重复出现,应尽量减少,可利用局部变量暂存查询结果。

此点可与循环结合。常需根据字符串或数组长度循环,而长度通常不变。若每次查询a.length均增加额外操作,预先var len=a.length则可减少一次查询。

字符串处理与类型转换技巧

字符串连接

追加字符串时,建议使用s+=anotherStr,而非s=s+anotherStr。

连接多个字符串时,应少用+=,例如:

s+=a;s+=b;s+=c;应改写为 s+=a + b + c;若需多次对同一字符串进行+=操作,建议使用缓存。具体做法是利用JavaScript数组收集,最后用join方法连接,如下:


var buf = new Array();for(var i = 0; i < 100; i++){ buf.push(i.toString());}var all = buf.join("");类型转换

类型转换是常见错误点,因JavaScript为动态类型语言,无法指定变量类型。

1. 数字转字符串,使用"" + 1,虽外观欠佳,但效率最高。性能排序: ("" +) > String() > .toString() > new String()

此条与下方“直接量”类似,编译时内部操作优于运行时用户操作。

String()属内部函数,速度较快;.toString()需查询原型函数,速度稍逊;new String()用于返回精确副本。

2. 浮点数转整型,易出错。许多人偏好parseInt(),但parseInt()用于字符串转数字,而非浮点与整型转换。应使用Math.floor()或Math.round()。 另,与第二节对象查找问题不同,Math为内部对象,Math.floor()查询与调用时间极少,速度最快。

3. 对于自定义对象,若定义了toString()进行类型转换,推荐显式调用toString()。因内部操作尝试所有可能性后,会尝试对象toString()方法转String,直接调用此方法效率更高。

直接量使用与对象构造优化

使用直接量

此影响较小,可忽略。所谓使用直接量,如JavaScript支持[param,param,param,...]直接表示数组,以往多用new Array(param,param,...)。前者由引擎直接解释,后者需调用Array内部构造器,故前者略快。

同理,var foo = {}比var foo = new Object();快,var reg = /../;比var reg=new RegExp()快。

高级对象与DOM操作优化

字符串遍历操作

对字符串进行循环操作(如替换、查找),应使用正则表达式。因JavaScript循环速度较慢,而正则表达式由C语言API实现,性能优异。

高级对象

自定义高级对象与Date、RegExp对象构造时消耗大量时间。若可复用,应采用缓存方式。

DOM相关优化

插入HTML

许多人喜欢在JavaScript中使用document.write生成页面内容,效率较低。若需直接插入HTML,可寻找容器元素(如div或span),设置其innerHTML插入代码。

对象查询

使用[“”]查询比.items()更快,与前述减少对象查找思路一致。调用.items()增加了一次查询和函数调用。

创建DOM节点

通常可能使用字符串直接写HTML创建节点,但这样做

无法保证代码有效性

字符串操作效率低

因此,应使用document.createElement()方法。若文档中存在现成样板节点,应使用cloneNode()方法。因createElement()需多次设置属性,cloneNode()可减少属性设置次数——若需创建大量元素,应预先准备样板节点。

定时器、引擎与文件优化策略

定时器

若针对持续运行代码,不应使用setTimeout,而应使用setInterval。setTimeout每次需重新设置定时器。

其他因素

脚本引擎

据测试,Microsoft JScript效率较Mozilla Spidermonkey差很多,无论执行速度还是内存管理,因JScript已基本不再更新。但SpiderMonkey不支持ActiveXObject。

文件优化

文件优化是有效手段。删除所有空格和注释,将代码放入一行,可加快下载速度。注意,这是下载速度而非解析速度。本地环境下,注释和空格不影响解释和执行速度。

总结

本文总结了JavaScript编程中提升运行性能的方法,基于以下原则:

直接使用现成资源更快,如局部变量比全局变量快,直接量比运行时构造对象快等。

尽可能减少执行次数,例如预先缓存需多次查询的内容。

尽可能使用语言内置功能,如字符串连接。

尽可能使用系统提供的API,因这些API为编译好的二进制代码,执行效率高。

同时,基本算法优化同样适用于JavaScript,如运算结构调整,此处不再赘述。因JavaScript为解释型,运行时一般不优化字节码,故这些优化仍重要。

当然,部分技巧同样适用于其他解释型语言,供参考。

因测试代码不全,补充部分如下:


var print; 
if(typeof document != "undefined" ){ 
  print = function(){ 
document.write(arguments[0]); 
} 
}else if(typeof WScript != "undefined" ){ 
  print = function(){ 
    WScript.Echo(arguments[0],arguments[1],arguments[2]); 
  } 
} 
function empty(){ 
} 
function benchmark(f){ 
  var i = 0; 
  var start = (new Date()).getTime(); 
  while(i < pressure){ 
    f(i++); 
  } 
  var end = (new Date()).getTime(); 
  WScript.Echo(end-start); 
} 
/* 
i=0 
start = (new Date()).getTime(); 
while(i < 60000){ 
  c = [i,i,i,i,i,i,i,i,i,i]; 
  i++; 
} 
end = (new Date()).getTime(); 
WScript.Echo(end-start); 
i=0 
start = (new Date()).getTime(); 
while(i < 60000){ 
  c = new Array(i,i,i,i,i,i,i,i,i,i); 
  i++; 
} 
var end = (new Date()).getTime(); 
WScript.Echo(end-start); 
*/ 
function internCast(i){ 
  return "" + i; 
} 
function StringCast(i){ 
  return String(i) 
} 
function newStringCast(i){ 
  return new String(i) 
} 
function toStringCast(i){ 
  return i.toString(); 
} 
function ParseInt(){ 
  return parseInt(j); 
} 
function MathFloor(){ 
  return Math.floor(j); 
} 
function Floor(){ 
  return floor(j); 
} 
var pressure = 50000; 
var a = ""; 
var floor = Math.floor; 
j = 123.123; 
print("-------------\nString Conversion Test"); 
print("The empty:", benchmark(empty)); 
print("intern:", benchmark(internCast)); 
print("String:"); 
benchmark(StringCast); 
print("new String:"); 
benchmark(newStringCast); 
print("toString:"); 
benchmark(toStringCast); 
print("-------------\nFloat to Int Conversion Test"); 
print("parseInt"); 
benchmark(ParseInt); 
print("Math.floor"); 
benchmark(MathFloor); 
print("floor") 
benchmark(Floor); 
function newObject(){ 
  return new Object(); 
} 
function internObject(){ 
  return {}; 
} 
print("------------\nliteral Test"); 
print("runtime new object", benchmark(newObject)); 
print("literal object", benchmark(internObject));  

附录2

代码1:


for(var i=0;i<100;i++){ 
    arr[i]=0; 
  }

代码2:


 var i = 0; 
  while(i < 100){ 
    arr[i++]=0; 
  } 

代码3:


 var i = 0; 
  while(i < 100){ 
    arr[i]=0; 
    i++; 
  }

在Firefox下测试这两段代码,结果代码2优于代码1和3,代码1一般优于代码3,有时被代码3超过;而在IE 6.0下,测试压力较大时(如10000次以上),代码2和3有时优于代码1,有时远落后;测试压力较小时(如5000次),代码2>代码3>代码1。

代码4:


var i = 0; 
  var a; 
  while(i < 100){ 
    a = 0; 
    i++; 
  } 

代码5:


 var a; 
  for(var i=0;i<100;i++){ 
    a = 0; 
  } 

上述两段代码在Firefox和IE下测试结果性能接近。

代码6:


  var a; 
  var i=0; 
  while(i<100){ 
    a=i; 
    i++; 
  } 

代码7:


 var a; 
  var i=0; 
  while(i<100){ 
    a=i++; 
  } 

代码8:


 var a; 
  for(var i=0;i<100;i++){ 
    a = i; 
  } 

代码9:


 var a; 
  for(var i=0;i<100;){ 
    a = i++; 
  } 

这四段代码在Firefox下6和8性能接近,7和9性能接近,而6, 8 < 7, 9;

最后查看空循环测试

代码10:


for(var i=0;i<100;i++){  } 

代码11:


  var i; 
  while(i<100){    i++;  } 

最后测试出现神奇结果,Firefox下代码10耗时与代码11耗时约24:1。故无参考价值,未放在开头展示。

作者最新文章