标签:
最近在写基于 JavaScript 的 Todos 的时候常常会需要遍历数组/类数组/对象的操作,一直以来都是使用的 for(var i = 0; i<length; i++) 这种写法,这次也是突发奇想使用了 for(var i in []/{}) 的遍历写法,然而给自己挖了巨大的陷阱,绕了很久才找到了错误。事后查了下资料,把它们的用法区别、效率差异记录下。
一、 基本用法
1. for(var i = 0; i<length; i++)
这种循环语句在各种编程语言中都是很重要的流程控制语句,是常见的前测试循环语句,就是说在循环体内的代码被执行之前,就会对条件进行求值,不符合的话就不会执行。主要就是使用在(类)数组遍历中。
var arr = [5,4,‘Hello‘,{}]; for(var i = 0; i < arr.length; i++){ console.log(i + ‘ : ‘ + arr[i]); }
上面就是典型的在数组遍历中的应用。突发奇想试试是不是可以遍历对象,发现连 obj.length 的值都是 undefined。这个还是交给 for-in 专业的来。
2. for-in
for-in 和前者有着本质上的不同,是一种迭代语句,用于枚举对象的(可枚举)属性,值得注意的是顺序也是存在 bug 的,浏览器的实现存在差异。枚举对象的可枚举属性,这段比较拗口,先留在后面解释,先谈谈它的常规用途。
var obj = { a : 1, b : ‘abc‘, c : {}, fn : function(){ console.log(‘666‘); } } for(var i in obj){ console.log(i + ‘ : ‘ + obj[i]); }
输出的结果是: a : 1 b : abc c : Object {} fn : function(){console.log(‘666‘);}
数组也是可以使用 for-in 遍历的其中 i 就代表的是数字索引值。值得可以的是,看代码:
var arr = [2,2,2]; arr.key = 3; for(var i in arr){ console.log(i); }
这里面最后会输出 key ,但是这种给数组指定属性的方式并不值得推崇。
基础的知识就扯到这里,感觉会有很多纰漏,大家多多担待。
二、 略深入的理解
1. 关于可枚举属性
在 JavaScript 的面向对象中,存在一概念为特性:描述了属性(property)的各种特征。无论是数据属性还是访问器属性(这里的数据属性、访问器属性分别对应其他面向对象语言中的公有变量和私有变量,因为我的理解有限,有兴趣的可以看 《JavaScript高级程序设计》 ),都实现了 [[Enumerable]] 特性,它规定了能否通过 for-in 循环返回属性。 像前面例子中那样直接在对象上定义的属性,这个特性的默认值是 true。
理论总是佶屈聱牙的,但是可以帮助我们理解特性。下面看看一些特殊的表现形式:
① 不会显示原型 prototype(__proto__) 上面的继承(预定义)属性
前面的例子中没有出现 obj 的诸如 .toString()、.valueOf() 属性,这些继承自 Object 的属性存在 obj 的原型上,他们是不可枚举的,所以 for-in 无法获取;
② 重写内置属性的复杂效果
我们试试重写 obj 的 .toString() :
var obj = { toString : function(){return "666"} }; for(var i in obj) { console.log(i); }
输出的结果是很讨厌复杂的:
- IE6/7/8 下面和没有重写 .toString() 一样,无法返回该重写属性;
- IE9/Firefox/Chrome/Opera/Safari 则可以返回 toString;
③ 给原型对象添加属性/方法,for-in 也是可以枚举的,这里就不写 Demo 了;
乍一看感觉这个并没有什么了解的必要,其中有个比较重要应用在于:在跨浏览器的设计中,我们不能依赖于for in来获取对象的成员名称,一般使用hasOwnProperty来判断下。比如为了使某些老旧的浏览器( IE6/7/8 等等)兼容 ES5 或者 ES6 的新特性,需要扩展内置构造器的原型。在 IE6/7/8 中没有实现 .bind() 函数,则需要在原型中进行扩展:
if (!Function.prototype.bind) { Function.prototype.bind = function(scope) { var fn = this; return function () { fn.apply(scope, arguments); } }; } function greet(name) { alert(this.greet + ‘, ‘ + name); } for (var i in greet) { console.log(i); }
这里在 IE6/7/8 中会输出 bind,现代浏览器因为原生支持 .bind() ,所以无法 for-in 到。
2. 关于类数组对象与 for-in
这里就要提到我遇坑的场景了。我使用了 for-in 遍历了 document.querySelectorAll(‘chosen‘) 的返回值。在控制台执行下面代码的时候,返回的值如图所示:
document.querySelectorAll(‘.todo_text‘);
我一看,这不是类数组嘛,然后就大张旗鼓的开始使用 for-in ,最后才发现错误的根源:
Sources 中的查询结果
控制台中的执行结果
事后翻了下 《JavaScript高级程序设计》 ,才发现其中对这个概念原本就有明确的解释:
NodeList 是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。请注意,虽然可以通过方括号语法来访问 NodeList 的值,而且这个对象也有 length 属性,但是它不是 Array 的实例。 NodeList 的特殊之处在于它实际上是基于 DOM 结构的动态执行查询的结果。可以使用 Array.prototype.slice() 方法将其转换为数组。
与之类似的,还有 jQuery 选择器返回的包装对象类数组对象,同样还是使用 for 循环老老实实遍历取值吧。
三、 关于执行效率
查询资料的过程中,发现很多关于执行效率的讨论。因为两者的适用环境是有不同也有相同,所以这里整理下。
主要是两者在(类)数组遍历中的效率差异:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>text</title> 5 </head> 6 <body> 7 <script type="text/javascript"> 8 function makeArr(num){ 9 var arr = []; 10 for(var i = 0; i < num; i++){ 11 arr.push(‘666‘); 12 } 13 return arr; 14 } 15 16 function ErgodicOne(){ 17 var arr = makeArr(10000000); 18 var count = 0; 19 var start = (new Date()).valueOf(); 20 for(var i = 0; i < arr.length; i++){ 21 count ++; 22 } 23 var end = (new Date()).valueOf(); 24 console.log(count + ‘:‘ + (end - start) + ‘ time‘); 25 } 26 27 function ErgodicTwo(){ 28 var arr = makeArr(10000000); 29 var count = 0; 30 var start = (new Date()).valueOf(); 31 for(var i in arr){ 32 count ++; 33 } 34 var end = (new Date()).valueOf(); 35 console.log(count + ‘:‘ + (end - start) + ‘ time‘); 36 } 37 38 function ErgodicThree(){ 39 var arr = makeArr(10000000); 40 var count = 0; 41 var start = (new Date()).valueOf(); 42 for(var i = 0, length = arr.length; i < length; i++){ 43 count++; 44 } 45 var end = (new Date()).valueOf(); 46 console.log(count + ‘:‘ + (end - start) + ‘ time‘); 47 } 48 49 function ErgodicFour(){ 50 var arr = makeArr(10000000); 51 var count = 0; 52 var start = (new Date()).valueOf(); 53 for(var i = arr.length - 1; i >= 0; i--){ 54 count++; 55 } 56 var end = (new Date()).valueOf(); 57 console.log(count + ‘:‘ + (end - start) + ‘ time‘); 58 } 59 </script> 60 </body> 61 </html>
其中用四种方式遍历长度为10000000的数组 Arr:
测试结果:
1. in Chrome (版本 47.0.2498.0)
值得注意的是,大多数情况下,除了 for-in ,其他三种情况的执行时间是差异很小的,但是偶尔会出现上图中 31 time 的情况;
2. in FireFox (版本 40.0.3)
和 Chrome 类似,除了 for-in 的事件消耗很大,其他的三种方式都是伯仲之间,但是也会和 Chrome 一样出现突然翻倍时间的情况;
3. in IE
其实一开始知道要测试 IE ,我是拒绝开心的。
版本是 IE11/ IE10/ IE9/ IE8/ IE7/IE5
这里只贴了一个结果图,是因为上面各种文档模式下,运行结果都和图中类似,没有明显优化。就是这样的规律:在 IE 中先将参数计算出来的效率最高,其次是倒序遍历,下来是正序遍历,最后是使用 for-in。
4. 小结
其实测试结果很好理解,第一种方法每次判断都需要获取一次 length 的值,在数量级高的判断中,还是非常的占用时间;其实方法三四的差异不大,在时间上面的反映也是比较的区别也是比较小;而 for-in 则关系到数据类型的转换以及属性特性的获取,自然消耗时间,使用的场景也是不准确;可以看出,在流行浏览器 Chrome、FireFox 中对除了方法二之外的方法的优化还是比较显著地,但是以后在开发中还是使用方法三是最保险的。
关于JavaScript中的for-in和for语法比较与效率区别
标签:
原文地址:http://www.cnblogs.com/WitNesS/p/4792916.html