高性能JavaScript——8、编程实践

避免双重求值( Double Evaluation)

JavaScript像其他很多脚本语言一样,允许你在程序中提取一个包含代码的字符串,然后动态执行它。有四个标准方法可以实现:eval()、Function()、setTimeout()和setInterval()。

    var num1 = 5,
    	num2 = 6,
	    // eval()
	    result = eval("num1 + num2"),
	    // Function()
	    sum = new Function("arg1", "arg2", "return arg1 + arg2");
    // setTimeout()
    setTimeout("sum = num1 + num2", 100);
    // setInterval()
    setInterval("sum = num1 + num2", 100);

当你在JavaScript代码中执行另一段JavaScript代码时,都会导致双重求值的性能消耗。此代码首先会以正常的方式求值,然后在执行过程中对包含于字符串中的代码发起另一个求值运算。双重求值是一项代价昂贵的操作, 它比直接包含的代码执行速度慢许多。

大多数情况下,没必要使用eval()和Function(),因此最好避免使用;至于另外两个函数:setTimeout ()和setlnterval(), 建议传入函数而不是字符串来作为第一个参数。

避免双重求值是实现JavaScript运行期性能最优化的关键所在。

使用Object/Array直接量

    // 较慢
    var myObject = new Object();
    myObject.name = "Nicholas";
    myObject.count = 50;
    myObject.flag = true;
    myObject.pointer = null;
    var myArray = new Array(); 
    myArray[0] = "Nicholas";
    myArray[1] = 50;
    myArray[2] = true;
    myArray[3] = null;
    // 较快
    var myObject = {
        name: "Nicholas",
        count: 50,
        flag: true,
        pointer: null
    };
    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);
        }
    }

隐藏的性能问题在于每次函数调用时都做了重复工作。因为每次的检查过程都相同:看看指定方法是否存在。

有几种方法可以避免它。

延迟加载

延迟加载意味着在信息使用前不会做任何操作。

    function addHandler(target, eventType, handler){
        // 复写现有函数
        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);
            };
        }
        // 调用新函数 
        addHandler(target, eventType, handler);
    }
    function removeHandler(target, eventType, handler){
        // 复写现有函数
        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);
            };
        }
        // 调用新函数
        removeHandler(target, eventType, handler);
    }

调用延迟加载函数时,第一次总会消耗较长的时间,但随后调用相同的函数就会更快,因为不需要再执行检测逻辑。

条件预加载

条件预加载会在脚本加载期间提前检测,而不会等到函数被调用。检测的操作依然只有一次,只是它在过程中来的更早。

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

条件预加载确保所有函数调用消耗的时间相同。 其代价是需要在脚本加载时就检测, 而不 是加载后。 预加载适用于一个函数马上就要被用到, 井且在整个页面的生命周期中频繁出现的场合。

使用速度快的部分

尽管JavaScript经常被指责运行缓慢, 但这门语言的某些部分运行却快得让人难以置信。 这并不足为奇, 因为JavaScript引擎是由低级语言构建的而且经过编译。 虽然JavaScript运行速度慢很容易被归咎于引擎, 然而引擎通常是处理过程中最快的部分,运行速度慢的实际上是你的代码。 引擎的某些部分比其他部分快很多, 因为它们允许你绕过那些慢的部分。

位操作

JavaScript中的数字按照IEEE-754标准64位格式存储。在位运算中,数字被转换为有符号32位格式。每种运算符均直接操作在这个32位数上实现结果。尽管需要转换,这个过程与JavaScript中其他数学和布尔运算相比还是非常快。

JavaScript 中有四种位逻辑操作符:

  1. Bitwise AND 位与:
    两个操作数的位都是 1,结果才是 1;
  2. Bitwise OR 位或:
    有一个操作数的位是 1,结果就是 1;
  3. Bitwise XOR 位异或:
    两个位中只有一个 1,结果才是 1;
  4. Bitwise NOT 位非:
    遇 0 返回 1,反之亦然。

原生方法

无论你如何优化JavaScript代码,都永远不会比JavaScript引擎提供的原生方法更快。道理很简单:JavaScript的原生部分在你写代码之前已经存在浏览器中了,并且都是低级语音编写的,诸如C++。这意味着这些方法会编译成机器码,成为浏览器的一部分,所以不会像自己写的JavaScript代码那样受到各种限制。

  • Math的各个方法
  • 原生的querySelector()和querySelectorAll()

小结

  • 避免使用eval()和Function()构造器来避免双重求值带来的性能消耗。同样的,给setTimeout()和setlnterval()传递函数而不是字符串作为参数。
  • 尽量使用直接量创建对象和数组。直接量的创建和初始化都比非直接量形式要快。
  • 避免做重复的工作。当需要检测浏览器时,可使用延迟加载或条件预加载。
  • 在进行数学计算时,考虑使用直接操作数字的二进制形式的位运算。
  • JavaScript的原生方住总会比你写的任何代码都要快。尽量使用原生方住。
  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值