避免双重求值( 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 中有四种位逻辑操作符:
- Bitwise AND 位与:
两个操作数的位都是 1,结果才是 1; - Bitwise OR 位或:
有一个操作数的位是 1,结果就是 1; - Bitwise XOR 位异或:
两个位中只有一个 1,结果才是 1; - Bitwise NOT 位非:
遇 0 返回 1,反之亦然。
原生方法
无论你如何优化JavaScript代码,都永远不会比JavaScript引擎提供的原生方法更快。道理很简单:JavaScript的原生部分在你写代码之前已经存在浏览器中了,并且都是低级语音编写的,诸如C++。这意味着这些方法会编译成机器码,成为浏览器的一部分,所以不会像自己写的JavaScript代码那样受到各种限制。
- Math的各个方法
- 原生的querySelector()和querySelectorAll()
小结
- 避免使用eval()和Function()构造器来避免双重求值带来的性能消耗。同样的,给setTimeout()和setlnterval()传递函数而不是字符串作为参数。
- 尽量使用直接量创建对象和数组。直接量的创建和初始化都比非直接量形式要快。
- 避免做重复的工作。当需要检测浏览器时,可使用延迟加载或条件预加载。
- 在进行数学计算时,考虑使用直接操作数字的二进制形式的位运算。
- JavaScript的原生方住总会比你写的任何代码都要快。尽量使用原生方住。