js性能问题

 

 
 首先,因为JS是一种解释型语言,执行速度要比编译型语言慢得多。(注:,Chrome是第一款内置优化引擎,将JS编译成本地代码的浏览器,其它浏览器也陆续实现了JS的编译过程。可是,即便到了编译执行JS的新阶段,仍然会存在低效率的代码。)如下总结一些能够改进代码的总体性能的方法。
 
1.注意做用域
 
记住一点,随着做用域中的做用域数量的增长,访问当前做用域之外的变量的时间也在增长。因此,访问全局变量老是比访问局部变量要慢,由于须要遍历做用域链。只要能减小花费在做用域链上的时间,就能增长脚本的总体性能。
 
1). 避免全局查找(由于涉及做用域上的查找)
 
function updateUI() {
    var imgs = document.getElementByTagName("img");
    for(var i = 0, len = imgs.length; i < len; i++) {
        imgs[i].title = document.title + " image " + i;
    }
}
注意,updateUI中包含了二个对于全局变量document对象的引用,特别是循环中的document引用,查到次数是O(n),每次都要进行做用域链查找。经过建立一个指向document的局部变量,就能够经过限制一次全局查找来改进这个函数的性能。
 
function updateUI() {
    var doc = document;
    var imgs = doc.getElementByTagName("img");
    for(var i = 0, len = imgs.length; i < len; i++) {
        imgs[i].title = doc.title + " image " + i;
    }
}
2). 避免with语句(with会建立自已的做用域,所以会增长其中执行代码的做用域的长度)
 
2.选择正确的方法
 
和其它语言同样,性能问题的一部分是和用于解决问题的算法或方法有关的,因此经过选择正确的方法也能起到优化做用。
 
1.避免没必要要的属性查找
 
在JS中访问变量或数组都是O(1)操做,比访问对象上的属性更有效率,后者是一个O(n)操做。对象上的任何属性查找都要比访问变量或数组花费更长时间,由于必须在原型链中对拥有该名称的属性进行一次搜索,即属性查找越多,执行时间越长。因此针对须要屡次用到对象属性,应将其存储在局部变量。
 
2.优化循环
 
循环是编程中最多见的结构,优化循环是性能优化过程当中很重要的一部分。一个循环的基本优化步骤以下:
 
减值迭代——大多数循环使用一个从0开始,增长到某个特定值的迭代器。在不少状况下,从最大值开始,在循环中不断减值的迭代器更加有效。
简化终止条件——因为每次循环过程都会计算终止条件,故必须保证它尽量快,即避免属性查找或其它O(n)的操做。
简化循环体——循环体是执行最多的,故要确保其被最大限度地优化。确保没有某些能够被很容易移出循环的密集计算。
使用后测试循环——最经常使用的for和while循环都是前测试循环,而如do-while循环能够避免最初终止条件的计算,因些计算更快。
for(var i = 0; i < values.length; i++) {
    process(values[i]);
}
优化1:简化终止条件
 
for(var i = 0, len = values.length; i < len; i++) {
    process(values[i]);
}
优化2:使用后测试循环(注意:使用后测试循环须要确保要处理的值至少有一个)
 
var i values.length - 1;
if(i > -1) {
    do {
        process(values[i]);
    }while(--i >= 0);
}
3.展开循环
 
当循环的次数肯定时,消除循环并使用屡次函数调用每每更快
当循环的次数不肯定时,可使用Duff装置来优化。Duff装置的基本概念是经过计算迭代的次数是否为8的倍数将一个循环展开为一系列语句。以下:
// Jeff Greenberg for JS implementation of Duff's Device
// 假设:values.length > 0
function process(v) {
    alert(v);
}
 
var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
var iterations = Math.ceil(values.length / 8);
var startAt = values.length % 8;
var i = 0;
 
do {
    switch(startAt) {
        case 0 : process(values[i++]);
        case 7 : process(values[i++]);
        case 6 : process(values[i++]);
        case 5 : process(values[i++]);
        case 4 : process(values[i++]);
        case 3 : process(values[i++]);
        case 2 : process(values[i++]);
        case 1 : process(values[i++]);
    }
    startAt = 0;
}while(--iterations > 0);
如上展开循环能够提高大数据集的处理速度。接下来给出更快的Duff装置技术,将do-while循环分红2个单独的循环。(注:这种方法几乎比原始的Duff装置实现快上40%。)
 
// Speed Up Your Site(New Riders, 2003)
function process(v) {
    alert(v);
}
 
var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;
 
if(leftover > 0) {
    do {
        process(values[i++]);
    }while(--leftover > 0);
}
 
do {
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
}while(--iterations > 0);
针对大数据集使用展开循环能够节省不少时间,但对于小数据集,额外的开销则可能得不偿失。
 
4.避免双重解释
 
当JS代码想解析JS代码时就会存在双重解释惩罚,当使用eval()函数或是Function构造函数以及使用setTimeout()传一个字符串时都会发生这种状况。以下
 
eval("alert('hello world');"); // 避免
var sayHi = new Function("alert('hello world');"); // 避免
setTimeout("alert('hello world');", 100);// 避免
以上代码是包含在字符串中的,即在JS代码运行的同时必须新启运一个解析器来解析新的代码。实例化一个新的解析器有不容忽视的开销,故这种代码要比直接解析要慢。如下这几个例子,除了极少状况下eval是必须的,应尽可能避免使用上述。对于Function构造函数,直接写成通常的函数便可。对于setTimeout能够传入函数做为第一个参数。以下:
 
alert('hello world');
var sayHi = function() {
    alert('hello world');
};
setTimeout(function() {
    alert('hello world');
}, 100);
总之,若要提升代码性能,尽量避免出现须要按照JS解释的代码。
 
5.性能的其它注意事项
 
原生方法更快——只要有可能,使用原生方法而不是自已用JS重写。原生方法是用诸如C/C++之类的编译型语言写出来的,要比JS的快多了。
switch语句较快——如有一系列复杂的if-else语句,能够转换成单个switch语句则能够获得更快的代码,还能够经过将case语句按照最可能的到最不可能的顺序进行组织,来进一步优化。
位运算较快——当进行数学运算时,位运算操做要比任何布尔运算或算数运算快。选择性地用位运算替换算数运算能够极大提高复杂计算的性能,诸如取模,逻辑与和逻辑或也能够考虑用位运算来替换。
3.最小化语句数
 
JS代码中的语句数量也会影响所执行的操做的速度,完成多个操做的单个语句要比完成单个操做的多个语句块快。故要找出能够组合在一块儿的语句,以减来总体的执行时间。这里列举几种模式
 
1.多个变量声明
 
// 避免
var i = 1;
var j = "hello";
var arr = [1,2,3];
var now = new Date();
 
// 提倡
var i = 1,
    j = "hello",
    arr = [1,2,3],
    now = new Date();
2.插入迭代值
 
// 避免
var name = values[i];
i++;
 
// 提倡
var name = values[i++];
3.使用数组和对象字面量,避免使用构造函数Array(),Object()
 
// 避免 
var a = new Array();
a[0] = 1;
a[1] = "hello";
a[2] = 45;
 
var o = new Obejct();
o.name = "bill";
o.age = 13;
 
// 提倡
var a = [1, "hello", 45];
var o = {
    name : "bill",
    age : 13
};
4.优化DOM交互
 
在JS中,DOM无疑是最慢的一部分,DOM操做和交互要消耗大量时间,由于它们每每须要从新渲染整个页面或者某一个部分,故理解如何优化与DOM的交互能够极大提升脚本完成的速度。
 
1.最小化现场更新
 
一旦你须要访问的DOM部分是已经显示的页面的一部分,那么你就是在进行一个现场更新。之因此叫现场更新,是由于须要当即(现场)对页面对用户的显示进行更新,每个更改,不论是插入单个字符仍是移除整个片断,都有一个性能惩罚,由于浏览器须要从新计算无数尺寸以进行更新。现场更新进行的越多,代码完成执行所花的时间也越长。
 
2.多使用innerHTML
 
有两种在页面上建立DOM节点的方法:使用诸如createElement()和appendChild()之类的DOM方法,以及使用innerHTML。对于小的DOM更改,二者效率差很少,但对于大的DOM更改,innerHTML要比标准的DOM方法建立一样的DOM结构快得多。
 
当使用innerHTML设置为某个值时,后台会建立一个HTML解释器,而后使用内部的DOM调用来建立DOM结构,而非基于JS的DOM调用。因为内部方法是编译好的而非解释执行,故执行的更快。
相关文章
相关标签/搜索