高效Javascript(节选)
1、避免使用 eval 或 Function 构造函数
每次 eval 或 Function 构造函数作用于字符串表示的源代码时,脚本引擎都需要将源代码转换成可执行代码。这是很消耗资
源的操作 —— 通常比简单的函数调用慢100倍以上。
eval 函数效率特别低,由于事先无法知晓传给 eval 的字符串中的内容,eval在其上下文中解释要处理的代码,也就是说编译
器无法优化上下文,因此只能有浏览器在运行时解释代码。这对性能影响很大。
Function 构造函数比 eval 略好,因为使用此代码不会影响周围代码;但其速度仍很慢。
2、避免使用全局变量
全局变量使用简单,因此很容易禁不住诱惑在脚本中使用全局变量。但有时全局变量也会影响脚本性能。
首先,如果函数或其他作用域内引用了全局变量,则脚本引擎不得不一级一级查看作用域直到搜索到全局作用域。查询本地作
用域变量更快。
其次,全局变量将始终存在在脚本生命周期中。而本地变量在本地作用域结束后就将被销毁,其所使用的内存也会被垃圾收集
器回收。
最后,window 对象也共享全局作用域,也就是说本质上是两个作用域而不是一个。使用全局变量不能像使用本地变量那样使用
前缀,因此脚本引擎要花更多时间查找全局变量。
3、注意隐式对象转换
字符串、数字和布尔值在 ECMAScript 中有两种表示方法。 每个类型都可以创建变量值或对象。
var oString = 'some content'//创建了字符串值
var oString = new String('some content')//创建了字符串对象。
所有的属性和方法都定义在 string 对象中,而不是 string 值中。每次使用 string 值的方法或属性, ECMAScript 引擎都
会隐式的用相同 string 值创建新的 string 对象。此对象只用于此请求,以后每次视图调用 string 值方法是都会重新创建。
下面的代码将要求脚本引擎创建21个新 string 对象,每次使用 length 属性时都会产生一个,每一个 charAt 方法也会产生
一个:
var s = '0123456789';
for( var i = 0; i < s.length; i++ ) {
s.charAt(i);
}
下面的代码和上面相同,但只创建了一个对象,因此其效率高:
var s = new String('0123456789');
for( var i = 0; i < s.length; i++ ) {
s.charAt(i);
}
4、在关键函数中避免 for-in
for-in 循环需要脚本引擎创建所有可枚举的属性列表,然后检查是否存在重复。
有时脚本已知可枚举的属性,这时简单的 for 循环即可遍历所有属性,特别是当使用顺序数字枚举时,如数组中。
5、优化 string 合并
字符串合并是比较慢的。+ 运算符并不管是否将结果保存在变量中。它会创建新 string 对象,并将结果赋给此对象;也许新
对象会被赋给某个变量。
下面是一个常见的字符串合并语句:
a += 'x' + 'y';
此代码首先创建临时string对象保存合并后的'xy'值,然后和a变量合并,最后将结果赋给a。下面的代码使用两条分开的命令
,但每次都直接赋值给a ,因此不 需要创建临时string对象。结果在大部分浏览器中,后者比前者快20%,而且消耗更少的内存:
a += 'x';
a += 'y';
6、基本运算符比函数调用更快
尽管单独使用效果不明显,但如果在需要高性能的关键循环和函数中使用基本运算符代替函数调用将可能提高脚本性能。
例子包括数组的 push 方法,其效率低于直接在数组末位赋值。另一个例子是 Math 对象方法,大部分情况下,简单的数学运
算符效率更高更合适。
var min = Math.min(a,b);
A.push(v);
下面代码实现相同功能,但效率更高:
var min = a < b ? a : b;
A[A.length] = v;
7、避免在遍历 DOM 时修改 DOM
有些 DOM 集合是实时的,如果在你的脚本遍历列表时相关元素产生变化,则此集合会立刻变化而不需要等待脚本遍历结束。childNodes 集合和 getElementsByTagName 返回的节点列表都是这样的实时集合。
如果在遍历这样的集合的同时向其中添加元素,则可能会遇到无限循环,因为你不停的向列表中添加元素,永远也不会碰到列表结束。这不是唯一的问题。为提高性能,可能会对这些集合做出优化,如记住其长度、记住脚本中上一个访问元素序号,这样在你访问下一个元素时可快速定位。
如果你此时修改 DOM 树,即使修改的元素不在此集合中,集合还是会重新搜索以查看是否有新元素。这样就无法记住上一个访问元素序号或记住集合长度,因为集合本身可能已经变了,这样就无法使用优化
var allPara = document.getElementsByTagName('p');
for( var i = 0; i < allPara.length; i++ ) {
allPara[i].appendChild(document.createTextNode(i));
}
下面的代码在 Opera 和 Internet Explorer 等主流浏览器中比上面代码快10倍以上。先创建一个要修改元素的静态列表,然后遍历静态列表并作出相应修改,而不是遍历 getElementsByTagName 返回的节点列表:
var allPara = document.getElementsByTagName('p');
var collectTemp = [];
for( var i = 0; i < allPara.length; i++ ) {
collectTemp[collectTemp.length] = allPara[i];
}
for( i = 0; i < collectTemp.length; i++ ) {
collectTemp[i].appendChild(document.createTextNode(i));
}
collectTemp = null;
8、动态创建 SCRIPT 元素
加载和处理脚本需要时间,但有些脚本在载入后却从来未被使用。载入这样的脚本浪费时间和资源,并影响当前的脚本执行,因此最好不要引用这种不用的脚本。可以通过简单的加载脚本判断需要哪些脚本,并只为后面需要的脚本创建 script 元素。
理论上,这个加载脚本可在页面载入结束后通过创建 SCRIPT 元素加入 DOM。这在所有主流浏览器中都可以正常工作,但这可能对浏览器的提出更多的要求,甚至大于要载入的脚本本身。而且在页面载入之前可能就需要脚本,因此最好在页面加载过程中,通过document.write 创建 script 标签。记住一定要转义‘/’字符防止终止当前脚本运行:
if( document.createElement && document.childNodes ) {
document.write('<script type="text\/javascript" src="dom.js"><\/script>');
}
if( window.XMLHttpRequest ) {
document.write('<script type="text\/javascript" src="xhr.js"><\/script>');
}
9、location.replace() 控制历史项
有时需要通过脚本修改页面地址。常见的方法是给 location.href 赋新地址。这将和打开新链接一样添加新历史项、载入新页面。
有时不想添加新历史项,因为用户不需要回到前面的页面。这在内存资源有限的设备中很有用。通过替换历史项恢复当前页面所使用的内存。可以通过 location.replace()方法实现。
此时页面仍被保存在 cache 中,仍占用内存,但比保存在历史中要少的多
10、使用 XPath 提高速度
假如需要基于 H2-H4 元素在 HTML 网页中创建目录。在 HTML 中标题元素可以出现在很多地方,因此无法使用递归函数获取这些元素。传统 DOM 可能使用如下方法
var allElements = document.getElementsByTagName('*');
for( var i = 0; i < allElements.length; i++ ) {
if( allElements[i].tagName.match(/^h[2-4]$/i) ) {
...
}
}
若网页有超过2000个元素,此方法速度会很慢。如果支持 XPath,则可以使用一个快得多的方法,因为 XPath 查询引擎可比需被解释的 JavaScript 更好的被优化。在有些情况下,XPath 速度可能会快2个数量级以上。下面代码和上面完成一样的功能,但使用 XPath 因此速度要更快:
var headings = document.evaluate( '//h2|//h3|//h4', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );
var oneheading;
while( oneheading = headings.iterateNext() ) {
...
}
下面版本代码融合上述两种方法;在支持 XPath 的地方使用快速方法,在不支持时使用传统 DOM 方法:
if( document.evaluate ) {
var headings = document.evaluate( '//h2|//h3|//h4', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );
var oneheading;
while( oneheading = headings.iterateNext() ) {
...
}
} else {
var allElements = document.getElementsByTagName('*');
for( var i = 0; i < allElements.length; i++ ) {
if( allElements[i].tagName.match(/^h[2-4]$/i) ) {
...
}
}
}