原文连接:https://dev.opera.com/articles/efficient-javascript/?page=2#primitiveo...javascript
曾经,一个Web页面不会包含太多的脚本,或者至少来讲,它们不会影响页面的性能。然而,如今的Web页面愈来愈像本地运用了,脚本的性能成了一个很大的影响随着愈来愈多的运用转向使用Web技术时,提升页面的性能成为了愈来愈重要的问题。java
eval
和Function构造函数
每次当eval
和Function constructor
经过字符串源码形式调用时,脚本引擎必须开启转换机制将字符串源码转换成可执行的代码。一般来讲这是比较耗性能的。浏览器
eval
调用特别的很差,当执行的内容字符串传递给eval
时不能被提早执行,因为代码执行会被eval
中执行的内容影响,那就意味着编译器不能更好的优化执行上下文,而且浏览器在运行时时放弃执行下面的上下文。这样就增长了额外的性能影响。函数
对于Function constructor
来讲,它的名声和eval
同样也不太好,虽然使用它并不会影响上下文的执行,可是它执行的效率却很低下。示例代码以下:性能
错误的使用eval
:优化
function getProperty(oString) { var oReference; eval('oReference = test.prop.' + oString); return oReference; }
正确的姿式:this
function getProperty(oString) { return test.prop[oString]; }
错误的使用Function constructor
:code
function addMethod(oObject, oProperty, oFunctionCode) { oObject[oProperty] = new Function(oFunctionCode); } addMethod( myObject, 'rotateBy90', 'this.angle = (this.angle + 90) % 360' ); addMethod( myObject, 'rotateBy60', 'this.angle = (this.angle + 60) % 360' );
正确的姿式:orm
function addMethod(oObject, oProperty, oFunction) { oObject[oProperty] = oFunction; } addMethod( myObject, 'rotateBy90', function() { this.angle = (this.angle + 90) % 360; } ); addMethod( myObject, 'rotateBy60', function() { this.angle = (this.angle + 60) % 360; } );
with
尽管对于开发人员来讲,使用with
比较方便,可是对性能来讲,倒是很是消耗的。缘由是对脚本引擎来讲,它会拓展做用域链,而查找变量的时候不会判断是否被当前引用。尽管这种状况带来性能的开销比较少,可是每次编译的时候咱们都不知道内容的做用域,那就意味着编译器不能对其进行优化,因此它就和普通的做用域同样。对象
一种更有效的方法代替方法是使用一个对象变量来代替with
的使用。属性的访问能够经过对象的引用来实现。这样工做起来很是有效,若是属性不是基本类型外,好比字符串和布尔值。
考虑下面的代码:
with(test.information.settings.files) { primary = 'names'; secondary = 'roles'; tertiary = 'references'; }
使用下面的方式效率更高:
var testObject = test.information.settings.files; testObject.primary = 'names'; testObject.secondary = 'roles'; testObject.tertiary = 'references';
try-catch-finally
try-catch-finally
语句相对于其余的语句来讲它的结构很是惟一的,当脚本运行的时候它会在当前的做用域总建立一个变量,这发生在catch
语句调用的时候.捕获的异常对象会关联这个变量,这个变量不会存在其余的脚本里,即便是相同的做用域。它在catch
语句开始的时候建立,在执行结束的时候销毁它。
由于这个变量会在运行的时候建立和销毁,因此会产生一种特殊的状况,一些浏览器不能及时的在性能比较耗的循环中及时捕获该句柄。因此会致使一些性能上的问题当异常被捕获的时候。
若是可能的话,异常的执行应该在更高的级别执行,这样它就不会频繁的出现,或者经过检查指望的动做最先被容许的话来避免,下面经过示例来讲明:
错误的使用方式:
var oProperties = [ 'first', 'second', 'third', … 'nth' ]; for(var i = 0; i < oProperties.length; i++) { try { test[oProperties[i]].someproperty = somevalue; } catch(e) { … } }
在许多状况下,try-catch-finally
结构能够移动到循环的外围, 这样看起来彷佛语意上有点改变。因为异常抛出时,循环会被中断,可是下面的代码会依然执行。
var oProperties = [ 'first', 'second', 'third', … 'nth' ]; try { for(var i = 0; i < oProperties.length; i++) { test[oProperties[i]].someproperty = somevalue; } } catch(e) { … }
在某些状况下,try-catch-finally
结构能够避免使用。好比:
var oProperties = [ 'first', 'second', 'third', … 'nth' ]; for(var i = 0; i < oProperties.length; i++) { if(test[oProperties[i]]) { test[oProperties[i]].someproperty = somevalue; } }
eval
和with
的使用因为这些结构影响性能如此的深,因此它们使用的越少越好。可是有时候你可能须要它们,若是一个函数被调用或者一个循环重复的被计算,最好的方式仍是避免使用这些结构,他们最好的解决方案就是被执行一次,或者极少数,以致于基本上对性能没什么影响。
在全局范围内建立一个变量是很诱惑的,只因它建立的方式很简单,可是有一下几个缘由会致使脚本运行变慢。
首先,全局变量须要脚本引擎查找到最外的做用域,查找速度比较慢,第二,全局变量经过window对象被分享,意味着本质上它有两层做用域(??)。
字面量,好比字符串,数字或者布尔值,在ECMAScript
中有两种表现,它门可能被看成单纯的值或者一个对象。
任何属性或者方法被调用的时候针对的是这个对象,不是这个值,当你引用一个属性或者方法的时候,ECMAScript
引擎会暗中的建立一个你值对应的字符串对象。在方法调用以前。这个对象只会被请求一次,当你尝试下一次调用该值的某个方法时它又会被建立一次。来看看下面的例子:
var s = '0123456789'; for(var i = 0; i < s.length; i++) { s.charAt(i); }
上面的例子须要脚本引擎建立21次字符串对象,一次length
属性的访问,一次charAt
方法的调用。
优化的方案以下所示:
var s = new String('0123456789'); for(var i = 0; i < s.length; i++) { s.charAt(i); }
和上面等效,可是仅仅手动建立了一个对象,性能上要比上面的好不少。
注意:不一样的浏览器,对于装箱和拆箱的优化不同。
for-in
迭代for-in
迭代有它本身的特色,可是它常常被滥用,这种迭代须要脚本引擎建立一个全部可枚举属性的清单,并检出为看成副本,在开始枚举的时候。
字符串的拼接是个昂贵的过程,当使用"+
"运算符时,它不会把结果当即添加到变量中,反而它会建立一个新的字符串对象在内存中,并把获得的结果赋值个这个字符串。而后这个新的字符串对象在赋值给变量。可是使用"+=
"能够避免这样的过程。
示例:
var min = Math.min(a,b); A.push(v);
下面的方式和上面的等效,可是效率更高:
var min = a < b ? a : b; A[A.length] = v;
setTimeout()
和setInterval()
当setTimeout()
和setInterval()
方法传递的是个字符串时,它内部会调用eval
,因此会致使性能上的问题。