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

  最近在写基于 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. 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 中对除了方法二之外的方法的优化还是比较显著地,但是以后在开发中还是使用方法三是最保险的。

时间: 2024-08-23 15:38:37

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

JavaScript中的bind,call和apply函数的用法和区别

一直没怎么使用过JavaScript中的bind,call和apply, 今天看到一篇比较好的文章,觉得讲的比较透彻,所以记录和总结如下 首先要理解的第一个概念,JavaScript中函数调用的方式,总结下来,有以下4种 1. 方法调用 2. 正常函数调用 3. 构造器函数调用 4. apply/call 调用 要明白的第2个概念, JavaScript 中的函数,无论是上面哪种函数调用方式,除了你函数声明时定义的形参外,还会自动给函数添加两个形参,分别是this 和 arguments 要明白

javascript中的innerHTML,innerText,outerHTML的用法及其区别

示例html代码: <div id="test"> <span style="color:red">test1</span> test2 </div> 获得id为test的DOM对象,下面就不一一获取了. var test = document.getElementById('test'); test.innerHTML 描述:也就是从对象的起始位置到终止位置的全部内容,包括Html标签. 上例中的test.inner

javascript 中 void 0的含义及undefine于void 0区别

undefined是一个全局属性,表示未定义或定义了没有赋值. void是一个一元运算符,不管传入什么参数都会返回undefined.  void操作符是在ECMAScript v1中定义的,而undefined是在ECMAScript v5中定义的. 我们知道undefined不是javascript的保留字,所以我们可以用undefined作为变量名.这时,我们定义的 undefined就会影响到使用undefined作为判断的地方.看下面例子: 测试了主流浏览器IE7-IE11.opera

什么是JavaScript中的面向对象? 与其他编程语言的面向对象有什么区别? 什么是原型?

面向对象与原型模式 1.1. js的对象: 定义:是"无序属性的集合,其属性可以包含基本值,对象,和函数",没有类的概念(其他面向对象的语言都有类的概念) 面向对象思维:把解决问题的关注点放在解决问题的所需对象上. 1.2. 面向对象的三大特性: 1.2.1. 封装 就是讲一系列属性和方法,也就是功能 ,封装在对象里面,对象对外界暴露一些接口,外界在使用的时候,不需要关心对象内部的具体功能. 1.2.2. 继承 # 其他面向对象语言中的继承:有父子关系. # JS中的继承:自己没有的东

Javascript中的&quot;函数是第一类对象(first-class object)&quot;

本身这句话很好解释,函数有两个主要特点,援引自 陈新 译的<JavaScript模式>: 1.函数是第一类对象: 函数可以在运行时动态创建,还可以在程序执行过程中创建. 函数可以分配变量,可以将它们的引用复制到其他变量,可以被扩展,此外,除少数特殊情况外,函数还可以被删除. 可以作为参数传递给其他函数,并且还可以有其他函数返回. 函数可以有自己的属性和方法. 2.函数提供了作用域 在JavaScript中,没有使用花括号{}语法来定义局部变量的作用域,也就是说,块并不能创建作用域.这也就意味着

JavaScript中的原型和继承

请在此暂时忘记之前学到的面向对象的一切知识.这里只需要考虑赛车的情况.是的,就是赛车. 最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事.最快的车被称为 Le Mans 原型车.这些车虽然是由"奥迪"或"标致"这些厂商制造的,可它们并不是你在街上或速公路上所见到的那类汽车.它们是专为参加高速耐力赛事而制造出来的. 厂家投入巨额资金,用于研发.设计.制造这些原型车,而工程师们总是努力尝试将这项工程做到极致.他们在合金.生物燃料.制动技术

【转】JavaScript中的原型和继承

请在此暂时忘记之前学到的面向对象的一切知识.这里只需要考虑赛车的情况.是的,就是赛车. 最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事.最快的车被称为 Le Mans 原型车.这些车虽然是由“奥迪”或“标致”这些厂商制造的,可它们并不是你在街上或速公路上所见到的那类汽车.它们是专为参加高速耐力赛事而制造出来的. 厂家投入巨额资金,用于研发.设计.制造这些原型车,而工程师们总是努力尝试将这项工程做到极致.他们在合金.生物燃料.制动技术.轮胎的化合物成分和安全特性上

JavaScript中的工厂方法、构造函数与class

JavaScript中的工厂方法.构造函数与class 本文转载自:众成翻译 译者:谢于中 链接:http://www.zcfy.cc/article/1129 原文:https://medium.com/javascript-scene/javascript-factory-functions-vs-constructor-functions-vs-classes-2f22ceddf33e#.wby148xu6 在ES6出现之前,人们常常疑惑JavaScript中的工厂模式和构造函数模式到底有

实现一个函数clone,使JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制

实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number.String.Object.Array.Boolean)进行值复制. 1 /** 对象克隆 2 * 支持基本数据类型及对象 3 * 递归方法 */ 4 function clone(obj) { 5 var o; 6 switch (typeof obj) { 7 case "undefined": 8 break; 9 case "string": o = obj + &q