码迷,mamicode.com
首页 > 编程语言 > 详细

关于JavaScript中的for-in和for语法比较与效率区别

时间:2015-09-09 12:59:00      阅读:222      评论:0      收藏:0      [点我收藏+]

标签:

  最近在写基于 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>
View Code

  其中用四种方式遍历长度为10000000的数组 Arr:

  1. ErgodicOne() 函数是正序 for 遍历数组;
  2. ErgodicTwo() 函数是 for-in 遍历数组;
  3. ErgodicThree() 函数是正序 for 遍历数组,但是先将判断条件参数计算出来;
  4. ErgodicFour() 函数是倒序 for 遍历数组;

  测试结果: 

  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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!