javascript性能优化(8)

Programming Practices 编程实践

避免二次评估

JavaScript 与许多脚本语言一样,允许你在程序中获取一个包含代码的字符串然后运行它。有四种标准 方法可以实现:eval(),Function()构造器,setTimeout()和 setInterval()。每个函数允许你传入一串 JavaScript 代码,然后运行它。例如

var num1 = 5, num2 = 6, //eval_r() evaluating a string of code
        result = eval("num1 + num2"), //Function() evaluating strings of code
        sum = new Function("arg1", "arg2", "return arg1 + arg2"); //setTimeout()evaluating a string of code
    setTimeout("sum = num1 + num2", 100); //setInterval() evaluating a string of code
    setInterval("sum = num1 + num2", 100);

结果都是11

避免二次评估是实现优化的 JavaScript 运行时性能的关键。
作为一个比较点,不同浏览器上访问一个数组项所占用的时间各有不同,但如果使用 eval()访问其结 果将大相径庭。例如:

 //faster 
 var item = array[0]; 
 //slower 
 var item = eval_r("array[0]"); 

若使用 eval()代替直接代码访问 10’000 个数组项,在不同浏览器上的差异非常巨大。是因为每次调用 eval()时要创建一个新的解释/编译实例。同样的过程 也发生在 Function(),setTimeout()和 setInterval()上,自动使代码执行速度变慢。
大多数情况下,没必要使用 eval()或 Function(),如果可能的话,尽量避免使用它们。至于另外两个函 数,setTimeout()和 setInterval(),建议第一个参数传入一个函数而不是一个字符串。例如:

    setTimeout(function () {
        sum = num1 + num2;
    }, 100);
    setInterval(function () {
        sum = num1 + num2;
    }, 100);

使用对象/数组直接量

典型的对象创建和赋值是这样的:

     //create an object 
    var myObject = new Object();
    myObject.name = "Nicholas";
    myObject.count = 50;
    myObject.flag = true;
    myObject.pointer = null;
    //create an array 
    var myArray = new Array();
    myArray[0] = "Nicholas";
    myArray[1] = 50;
    myArray[2] = true;
    myArray[3] = null;

但是,直接量赋值很快。作为一个额外的好处,直接量在你的代码中占 用较少空间,所以整个文件尺寸可以更小。

    //create an object 
    var myObject = {name: "Nicholas", count: 50, flag: true, pointer: null};
    //create an array 
    var myArray = ["Nicholas", 50, true, null];

不要重复工作

也许常见的重复工作类型是浏览器检测。大量代码依赖于浏览器的功能。以事件句柄的添加和删除为 例,典型的跨浏览器代码如下:

function addHandler(target, eventType, handler) {
        if (target.addEventListener) { //DOM2 Events    
            target.addEventListener(eventType, handler, false);
        } else { //IE     
            target.attachEvent("on" + eventType, handler);
        }
    }
    function removeHandler(target, eventType, handler) {
        if (target.removeEventListener) { //DOM2 Events    
            target.removeEventListener(eventType, handler, false);
        } else { //IE     
            target.detachEvent("on" + eventType, handler);
        }
    }

乍一看,这些函数为实现它们的目的已经足够优化。隐藏的性能问题在于每次函数调用时都执行重复工 作。每一次,都进行同样的检查,看看某种方法是否存在。如果你假设 target 唯一的值就是 DOM 对象, 而且用户不可能在页面加载时魔术般地改变浏览器,那么这种判断就是重复的。如果 addHandler()一上来 就调用addEventListener()那么每个后续调用都要出现这句代码。在每次调用中重复同样的工作是一种浪费, 有多种办法避免这一点。

延迟加载

第一种消除函数中重复工作的方法称作延迟加载。延迟加载意味着在信息被使用之前不做任何工作。在
前面的例子中,不需要判断使用哪种方法附加或分离事件句柄,直到有人调用此函数。使用延迟加载的函
数如下:

 function addHandler(target, eventType, handler) {   
        //overwrite the existing function   
        if (target.addEventListener) { //DOM2 Events    
            addHandler = function (target, eventType, handler) {
                target.addEventListener(eventType, handler, false);
            };
        } else { //IE     
            addHandler = function (target, eventType, handler) {
                target.attachEvent("on" + eventType, handler);
            };
        }   
        //call the new function 
        addHandler(target, eventType, handler);
    }
    function removeHandler(target, eventType, handler) {   
        //overwrite the existing function   
        if (target.removeEventListener) { //DOM2 Events     
            removeHandler = function (target, eventType, handler) {
                target.addEventListener(eventType, handler, false);
            };
        } else { //IE     
            removeHandler = function (target, eventType, handler) {
                target.detachEvent("on" + eventType, handler);
            };
        }   
        //call the new function   
        removeHandler(target, eventType, handler);
    }

这两个函数依照延迟加载模式实现。这两个方法第一次被调用时,检查一次并决定使用哪种方法附加或分离事件句柄。然后,原始函数就被包含适当操作的新函数覆盖了。后调用新函数并将原始参数传给它。以后再调用 addHandler()或者 removeHandler()时不会再次检测,因为检测代码已经被新函数覆盖了。
调用一个延迟加载函数总是在第一次使用较长时间,因为它必须运行检测然后调用另一个函数以完成任务。但是,后续调用同一函数将快很多,因为不再执行检测逻辑了。延迟加载适用于函数不会在页面上立即被用到的场合。

条件预加载

它在脚本加载之前提前进行检查,而不等待函数调用。 这样做检测仍只是一次,但在此过程中来的更早。例如:

 var addHandler = document.body.addEventListener ? function (target, eventType, handler) {
            target.addEventListener(eventType, handler, false);
        } : function (target, eventType, handler) {
            target.attachEvent("on" + eventType, handler);
        };
    var removeHandler = document.body.removeEventListener ? function (target, eventType, handler) {
            target.removeEventListener(eventType, handler, false);
        } : function (target, eventType, handler) {
            target.detachEvent("on" + eventType, handler);
        };

这个例子检查 addEventListener()和 removeEventListener()是否存在,然后根据此信息指定合适的函数。 三元操作符返回 DOM 级别 2 的函数,如果它们存在的话,否则返回 IE 特有的函数。然后,调用 addHandler() 和 removeHandler()同样很快,虽然检测功能提前了。
条件预加载确保所有函数调用时间相同。其代价是在脚本加载时进行检测。预加载适用于一个函数马上 就会被用到,而且在整个页面生命周期中经常使用的场合。

位操作运算符

计算对 2 取模,需要用这个数除以 2 然后查看余数。如果你看到 32 位数字的底层(二进制)表示法, 你会发现偶数的低位是 0,奇数的低位是 1。如果此数为偶数,那么它和 1 进行位与操作的结果就是 0; 如果此数为奇数,那么它和 1 进行位与操作的结果就是 1。也就是说上面的代码可以重写如下:

     for (var i = 0, len = rows.length; i < len; i++) {
        if (i & 1) {
            className = "odd";  //奇数
        } else {
            className = "even";//偶数
        }
        //apply class
    }

比取余运算要快;i%2;


第二种使用位操作的技术称作位掩码。位掩码在计算机科学中是一种常用的技术,可同时判断多个布尔 选项,快速地将数字转换为布尔标志数组。掩码中每个选项的值都等于 2 的幂。例如:

    var OPTION_A = 1;
    var OPTION_B = 2;
    var OPTION_C = 4;
    var OPTION_D = 8;
    var OPTION_E = 16;
    var options = OPTION_A | OPTION_C | OPTION_D;
    //is option A in the list?
    if (options & OPTION_A) {
        //do something
        console.log(options);
    }
    //is option B in the list?
    if (options & OPTION_D) {
        //do something
        console.log(options);
    }

结果:13 13
像这样的位掩码操作非常快,正因为前面提到的原因,操作发生在系统底层。如果许多选项保存在一起 并经常检查,位掩码有助于加快整体性能。


原生方法

  • 尽量使用Math对象,避免大量的数学逻辑;
  • 使用原生的选择器 API;如 querySelector()和 querySelectorAll();

Summary 总结

  • 通过避免使用 eval_r()和 Function()构造器避免二次评估。此外,给 setTimeout()和 setInterval()传递函数参 数而不是字符串参数
  • 创建新对象和数组时使用对象直接量和数组直接量。它们比非直接量形式创建和初始化更快。
  • 避免重复进行相同工作。当需要检测浏览器时,使用延迟加载或条件预加载。
  • 当执行数学运算时,考虑使用位操作,它直接在数字底层进行操作。
  • 原生方法总是比 JavaScript 写的东西要快。尽量使用原生方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值