导言
差不多所有的 js 库都会对原生的 js 和 DOM 模型进行封装和扩展, jQuery 、 prototype 、 Ext 都有类似做法。
由于这些原生对象的相关方法注定会被大量地调用,对它们扩展得好与不好会直接关系到整个类库整体的性能。
经常看到有人说 Ext 的执行效率低下,笔者有时也有这种感觉,点个鼠标好像要延迟一下才有反应。但是说话得要有根据不是,不知道慢在哪里也不能跟着瞎起哄。(当然,也免得文章发出来被别人拍板砖,已经被拍过几次,汗啊 … )。
前一部分文章说了一下怎么用 VML 直接在网页上绘图,并且封装了一个小工具。这一部分的内容就先来看一下 Ext 对 js 的原生对象是如何封装和扩展的。
概览
从截图可以看到,在 Ext.js 这个文件中, Ext 对 Function 、 String 、 Number 、 Array 、 Date 。五个原生对象进行了扩展。这一篇先来分析一下对 Array 对象的扩展,并提出一个小小的疑惑。
Array 对象
js 的原生 Array 对象包含一个 length 属性和 12 个方法,简表如下:
concat() :添加元素
join() :将数组中的元素连接成一个字符串并返回
pop() :弹出最后一个元素
push(): 把一个元素加到数组最后
reverse(): 颠倒顺序
shift(): 移除第一个元素
slice() :返回子数组
splice(): 插入、删除或替换一个元素
toLocaleString(): 把数组转换成一个本地化的字符串
toString() :把数组转化成一个字符串
unshift() :在数组头部插入一个字符串
以上引自《 JavaScript 权威指南 第五版》,加粗部分是自己对照英文版的的翻译,原来的翻译是“把数组转换成一个局部字符串 ”,意思非常模糊,让人搞不懂。顺便再罗嗦一下,第五版的翻译不知道是干什么吃的,很多的东西不翻译或者直译,漏洞百出,建议想买这本书的人不要买这个版本。
在 Ext.js 里给 Array 对象扩展了两个方法,代码如下(注释已经翻译成中文):
Ext.applyIf(Array.prototype, {
/**
* 查找数组中是否存在指定的对象
* @param {Object} 需要查找的对象(的引用)
* @return {Number} 对象在数组中的位置下标 ( 如果不存在该对象,返回 -1)
*/
indexOf : function(o){
// 注意这里:是简单的线性查找,拿数组中的元素一个一个地去对比。
for (var i = 0, len = this.length; i < len; i++){
if(this[i] == o) return i;
}
return -1;
},
/**
* 从数组中删除指定的元素,如果该元素不存在,则什么事情都不做。
* @param {Object} o 需要删除对象的引用
* @return {Array} 返回删除后的数组
*/
remove : function(o){
var index = this.indexOf(o);
if(index != -1){
this.splice(index, 1);
}
return this;
}
});
在注释里面加注意的地方说到 indexOf() 方法的查找问题,可以看到,这里的 indexOf() 使用的是简单的线性查找, remove() 方法调用了这个 indexOf() 方法来查找对象在数组中的位置。这个地方的效率很低,也就是说,如果我们往一个数组里面保存了很多的对象,然后在另外的地方频繁调用数组的 indexOf() 或者 remove() 方法的时候,速度就会比较慢。所以有人老说 Ext 存在效率问题,我想,这个所谓的“效率问题”不是哪一处地方造就的,而是由诸多效率差的小细节积累而成的,假如我们实在没有办法去改变什么,那么至少要注意到这些地方,避免陷阱,这是这篇文的主旨所在。
由于线性查找效率是 O(n) ,所以,在数据量稍大的时候,需要寻找替代 Array 的办法。有很多文章说过关于 Array 的这个问题,包括《权威指南》,办法是模拟一个 Hash 表。基本的思路如下:
var mp={};
mp['key1']={name:'damoqiongqiu',age:'25',QQ:'88403922'};
mp['key2']={name:'damoqiongqiu',age:'26',QQ:'88403922'};
alert(mp.key2.name);
这样处理,可以提升查找效率,效率可能接进 O(1) 。(顺便说一句,效率只能接进是 O(1) ,因为 js 内部通过这个字符串型的 key 去找到 value 也是需要花时间的,即使是通过类似Java或C++的Hash算法,也是需要通过key来算HashCode的不是?通过HashCode找到对应的Hash桶之后还是一个线性查找。)
《指南》里面还有提到一种伪数组:
var a={}
while(i<10){
a[i]=i*I;
i++
}
a.length=i;
如果情况比较好,事先知道插入的元素是有序的就可以使用其它的查找算法,像二分查找之类的,这样的情况估计不容易遇到,除非上天恩赐 J ,否则还讨论个什么劲?
当然,有些情况必须用到数组,除此没有办法,那还有什么好说的,用呗!但是,在确确实实对效率有很多要求的地方,需要考虑类似的变通方法。
一个疑惑:
经常看到有人问, js 里面的 == 和 === 有什么区别,翻了一下书,大概的意思是:
=== 是比较两个值是不是完全相等。
而如果用 == 来比较,假如两个值是同一类型,就用 === 比较它们是否等同。否则如果两个值是以下组合时,将非 Number 型强转成 Nunber 再来比较。
String 、 Number
Number 、 Boolean
也就是说, ”1”==true 是正确的, 1==true 也是正确的,如此这般。
如果一个值是对象,另一个值是 String 时,将对象转成 String ,再来比较这两个 String 是不是相等。
OK ,有了上面的简单解释,我们来测试一下 Ext 中 Array 的 indexOf() 方法。
首先来个简单的:
Ext.onReady(function(){
// 基础语义测试
var test="1";
alert(test==true);
})
没有问题,出来一个 true 。
再来个稍微复杂点的,先向数组里面插入 1000 个对象,然后在第六个元素放一个布尔型的 true 值,然后自己来遍历数组,看看有没有元素和字符型的 ’1’ 相等:
Ext.onReady(function(){
// 自定义数组测试
var test="1";
var arr=[];
for(var i=0;i<1000;i++){
var temp=(i==5)?true:{id:i};
arr.push(temp)
}
for (var i = 0, len = arr.length; i < len; i++){
if(arr[i] ==test){
alert(i);
break;
}
}
})
好,没有问题,找到了第六个元素。
来测 Array 的 indexOf() 方法,从 Ext 的源码里面可以看到, Array 的 indexOf() 方法和我们上面自己遍历的方法是一样的,但是,神奇的事情发生了,看代码:
Ext.onReady(function(){
// 自定义数组测试
var test="1";
var arr=[];
for(var i=0;i<1000;i++){
var temp=(i==5)?true:{id:i};
arr.push(temp)
}
// 测试 Ext 封装 Array 的 indexOf() 方法
alert(arr.indexOf(test));
})
按道理来说,这里也应该能够弹出来元素的位置,但是,结果是,没有找到这个元素。。。可视,这是为什么呢?期待牛人给出解释,不甚感谢!
这个问题已解决,多谢zbm2001的解释,我自己又测试了一下,只有FireFox实现了Array原生的indexOf()方法,而IE、Safari、Opera都没有原生实现。Ext在扩展的时候用的是applyIf()方法,所以在存在原生实现的时候不会拿Ext的实现去覆盖。
2 楼 damoqiongqiu 2010-11-30 20:14
1 楼 liusu 2010-06-06 22:27