1

原文链接:https://dev.opera.com/articles/efficient-javascript/?page=2#primitiveo...

高效的JavaScript

曾经,一个Web页面不会包含太多的脚本,或者至少来说,它们不会影响页面的性能。然而,现在的Web页面越来越像本地运用了,脚本的性能成了一个很大的影响随着越来越多的运用转向使用Web技术时,提高页面的性能成为了越来越重要的问题。

ECMAScript

避免使用evalFunction构造函数

每次当evalFunction constructor通过字符串源码形式调用时,脚本引擎必须开启转换机制将字符串源码转换成可执行的代码。通常来说这是比较耗性能的。

eval调用特别的不好,当执行的内容字符串传递给eval时不能被提前执行,由于代码执行会被eval中执行的内容影响,那就意味着编译器不能更好的优化执行上下文,并且浏览器在运行时时放弃执行下面的上下文。这样就增加了额外的性能影响。

对于Function constructor来说,它的名声和eval一样也不太好,虽然使用它并不会影响上下文的执行,但是它执行的效率却很低下。示例代码如下:

错误的使用eval:

function getProperty(oString) {
    var oReference;
    eval('oReference = test.prop.' + oString);
    return oReference;
}

正确的姿势:

function getProperty(oString) {
    return test.prop[oString];
}

错误的使用Function constructor

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'
);

正确的姿势:

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;
    }
}

隔离evalwith的使用

由于这些结构影响性能如此的深,所以它们使用的越少越好。但是有时候你可能需要它们,如果一个函数被调用或者一个循环重复的被计算,最好的方式还是避免使用这些结构,他们最好的解决方案就是被执行一次,或者极少数,以至于基本上对性能没什么影响。

避免使用全局变量

在全局范围内创建一个变量是很诱惑的,只因它创建的方式很简单,但是有一下几个原因会导致脚本运行变慢。

首先,全局变量需要脚本引擎查找到最外的作用域,查找速度比较慢,第二,全局变量通过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,所以会导致性能上的问题。


两仪
9.6k 声望729 粉丝

向上努力、不卑不亢、两仪相生