深入理解JavaScript中的for循环


Posted in Javascript onFebruary 07, 2017

本文对一个循环分享的这么详细,相信看完你会有所收获。下面话不多说,来看看详细的介绍吧。

在ECMAScript5(简称 ES5)中,有三种 for 循环,分别是:

  • 简单for循环
  • for-in
  • forEach

在2015年6月份发布的ECMAScript6(简称 ES6)中,新增了一种循环,是:

  • for-of

下面我们就来看看这 4 种 for 循环。

简单 for 循环

下面先来看看大家最常见的一种写法:

const arr = [1, 2, 3];
for(let i = 0; i < arr.length; i++) {
 console.log(arr[i]);
}

当数组长度在循环过程中不会改变时,我们应将数组长度用变量存储起来,这样会获得更好的效率,下面是改进的写法:

const arr = [1, 2, 3];
for(let i = 0, len = arr.length; i < len; i++) {
 console.log(arr[i]);
}

for-in

通常情况下,我们可以用 for-in 来遍历一遍数组的内容,代码如下:

const arr = [1, 2, 3];
let index;
for(index in arr) {
 console.log("arr[" + index + "] = " + arr[index]);
}

一般情况下,运行结果如下:

arr[0] = 1
arr[1] = 2
arr[2] = 3

但这么做往往会出现问题。

for-in 的真相

for-in 循环遍历的是对象的属性,而不是数组的索引。因此, for-in 遍历的对象便不局限于数组,还可以遍历对象。

例子如下:

const person = {
 fname: "san",
 lname: "zhang",
 age: 99
};
let info;
for(info in person) {
 console.log("person[" + info + "] = " + person[info]);
}

结果如下:

person[fname] = san
person[lname] = zhang
person[age] = 99

需要注意的是, for-in 遍历属性的顺序并不确定,即输出的结果顺序与属性在对象中的顺序无关,也与属性的字母顺序无关,与其他任何顺序也无关。

Array 的真相

Array 在 Javascript 中是一个对象, Array 的索引是属性名。事实上, Javascript 中的 “array” 有些误导性, Javascript 中的 Array 并不像大部分其他语言的数组。首先, Javascript 中的 Array 在内存上并不连续,其次, Array 的索引并不是指偏移量。实际上, Array 的索引也不是 Number 类型,而是 String 类型的。我们可以正确使用如 arr[0] 的写法的原因是语言可以自动将 Number 类型的 0 转换成 String 类型的 “0″ 。所以,在 Javascript 中从来就没有 Array 的索引,而只有类似 “0″ 、 “1″ 等等的属性。有趣的是,每个 Array 对象都有一个 length 的属性,导致其表现地更像其他语言的数组。但为什么在遍历 Array 对象的时候没有输出 length 这一条属性呢?那是因为 for-in 只能遍历“可枚举的属性”, length 属于不可枚举属性,实际上, Array 对象还有许多其他不可枚举的属性。

现在,我们再回过头来看看用 for-in 来循环数组的例子,我们修改一下前面遍历数组的例子:

const arr = [1, 2, 3];
arr.name = "Hello world";
let index;
for(index in arr) {
 console.log("arr[" + index + "] = " + arr[index]);
}

运行结果是:

arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[name] = Hello world

我们看到 for-in 循环访问了我们新增的 “name” 属性,因为 for-in 遍历了对象的所有属性,而不仅仅是“索引”。同时需要注意的是,此处输出的索引值,即 “0″、 “1″、 “2″不是 Number 类型的,而是 String 类型的,因为其就是作为属性输出,而不是索引。那是不是说不在我们的 Array 对象中添加新的属性,我们就可以只输出数组中的内容了呢?答案是否定的。因为 for-in 不仅仅遍历 array 自身的属性,其还遍历 array 原型链上的所有可枚举的属性。

下面我们看个例子:

Array.prototype.fatherName = "Father";
const arr = [1, 2, 3];
arr.name = "Hello world";
let index;
for(index in arr) {
 console.log("arr[" + index + "] = " + arr[index]);
}

运行结果是:

arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[name] = Hello world
arr[fatherName] = Father

写到这里,我们可以发现 for-in 并不适合用来遍历 Array 中的元素,其更适合遍历对象中的属性,这也是其被创造出来的初衷。却有一种情况例外,就是稀疏数组。

考虑下面的例子:

let key;
const arr = [];
arr[0] = "a";
arr[100] = "b";
arr[10000] = "c";
for(key in arr) {
 if(arr.hasOwnProperty(key) && 
 /^0$|^[1-9]\d*$/.test(key) && 
 key <= 4294967294 
 ) {
 console.log(arr[key]);
 }
}

for-in 只会遍历存在的实体,上面的例子中, for-in 遍历了3次(遍历属性分别为”0″、 “100″、 “10000″的元素,普通 for 循环则会遍历 10001 次)。所以,只要处理得当, for-in 在遍历 Array 中元素也能发挥巨大作用。

为了避免重复劳动,我们可以包装一下上面的代码:

function arrayHasOwnIndex(array, prop) {
 return array.hasOwnProperty(prop) && 
 /^0$|^[1-9]\d*$/.test(prop) && 
 prop <= 4294967294; // 2^32 - 2
}

使用示例如下:

for (let key in arr) {
 if (arrayHasOwnIndex(arr, key)) {
 console.log(arr[key]);
 }
}

for-in 性能

正如上面所说,每次迭代操作会同时搜索实例或者原型属性, for-in 循环的每次迭代都会产生更多开销,因此要比其他循环类型慢,一般速度为其他类型循环的 1/7。因此,除非明确需要迭代一个属性数量未知的对象,否则应避免使用 for-in 循环。如果需要遍历一个数量有限的已知属性列表,使用其他循环会更快,比如下面的例子:

const obj = {
 "prop1": "value1",
 "prop2": "value2"
};
 
const props = ["prop1", "prop2"];
for(let i = 0; i < props.length; i++) {
 console.log(obj[props[i]]);
}

上面代码中,将对象的属性都存入一个数组中,相对于 for-in 查找每一个属性,该代码只关注给定的属性,节省了循环的开销和时间。

forEach

在 ES5 中,引入了新的循环,即 forEach 循环。

const arr = [1, 2, 3];
arr.forEach((data) => {
 console.log(data);
});

运行结果:

1
2
3

forEach 方法为数组中含有有效值的每一项执行一次 callback 函数,那些已删除(使用 delete 方法等情况)或者从未赋值的项将被跳过(不包括那些值为 undefined 或 null 的项)。 callback 函数会被依次传入三个参数:

  1. 数组当前项的值;
  2. 数组当前项的索引;
  3. 数组对象本身;

需要注意的是,forEach 遍历的范围在第一次调用 callback 前就会确定。调用forEach 后添加到数组中的项不会被 callback 访问到。如果已经存在的值被改变,则传递给 callback 的值是 forEach 遍历到他们那一刻的值。已删除的项不会被遍历到。

const arr = [];
arr[0] = "a";
arr[3] = "b";
arr[10] = "c";
arr.name = "Hello world";
arr.forEach((data, index, array) => {
 console.log(data, index, array);
});

运行结果:

a 0 ["a", 3: "b", 10: "c", name: "Hello world"]
b 3 ["a", 3: "b", 10: "c", name: "Hello world"]
c 10 ["a", 3: "b", 10: "c", name: "Hello world"]

这里的 index 是 Number 类型,并且也不会像 for-in 一样遍历原型链上的属性。

所以,使用 forEach 时,我们不需要专门地声明 index 和遍历的元素,因为这些都作为回调函数的参数。

另外,forEach 将会遍历数组中的所有元素,但是 ES5 定义了一些其他有用的方法,下面是一部分:

  • every: 循环在第一次 return false 后返回
  • some: 循环在第一次 return true 后返回
  • filter: 返回一个新的数组,该数组内的元素满足回调函数
  • map: 将原数组中的元素处理后再返回
  • reduce: 对数组中的元素依次处理,将上次处理结果作为下次处理的输入,最后得到最终结果。

forEach 性能

首先感谢@papa pa的提醒,才发现我之前的理解有错误。

大家可以看 jsPerf ,在不同浏览器下测试的结果都是 forEach 的速度不如 for。如果大家把测试代码放在控制台的话,可能会得到不一样的结果,主要原因是控制台的执行环境与真实的代码执行环境有所区别。

for-of

先来看个例子:

const arr = ['a', 'b', 'c'];
for(let data of arr) {
 console.log(data);
}

运行结果是:

a
b
c

为什么要引进 for-of?

要回答这个问题,我们先来看看ES6之前的 3 种 for 循环有什么缺陷:

  • forEach 不能 break 和 return;
  • for-in 缺点更加明显,它不仅遍历数组中的元素,还会遍历自定义的属性,甚至原型链上的属性都被访问到。而且,遍历数组元素的顺序可能是随机的。

所以,鉴于以上种种缺陷,我们需要改进原先的 for 循环。但 ES6 不会破坏你已经写好的 JS 代码。目前,成千上万的 Web 网站依赖 for-in 循环,其中一些网站甚至将其用于数组遍历。如果想通过修正 for-in 循环增加数组遍历支持会让这一切变得更加混乱,因此,标准委员会在 ES6 中增加了一种新的循环语法来解决目前的问题,即 for-of 。

那 for-of 到底可以干什么呢?

  • 跟 forEach 相比,可以正确响应 break, continue, return。
  • for-of 循环不仅支持数组,还支持大多数类数组对象,例如 DOM nodelist 对象。
  • for-of 循环也支持字符串遍历,它将字符串视为一系列 Unicode 字符来进行遍历。
  • for-of 也支持 Map 和 Set (两者均为 ES6 中新增的类型)对象遍历。

总结一下,for-of 循环有以下几个特征:

  • 这是最简洁、最直接的遍历数组元素的语法。
  • 这个方法避开了 for-in 循环的所有缺陷。
  • 与 forEach 不同的是,它可以正确响应 break、continue 和 return 语句。
  • 其不仅可以遍历数组,还可以遍历类数组对象和其他可迭代对象。

但需要注意的是,for-of循环不支持普通对象,但如果你想迭代一个对象的属性,你可以用

for-in 循环(这也是它的本职工作)。

最后要说的是,ES6 引进的另一个方式也能实现遍历数组的值,那就是 Iterator。上个例子:

const arr = ['a', 'b', 'c'];
const iter = arr[Symbol.iterator]();
 
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

不过,这个内容超出了本文的范围,而且 Iterator 要讲的也有很多,以后有时间专门写一篇文章介绍,请大家继续关注三水点靠木。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

Javascript 相关文章推荐
jQuery 美元符冲突的解决方法
Mar 28 Javascript
JavaScript实现拼音排序的方法
Nov 20 Javascript
Javascript中自动切换焦点实现代码
Dec 15 Javascript
EasyUI 结合JS导出Excel文件的实现方法
Nov 10 Javascript
JS实现根据用户输入分钟进行倒计时功能
Nov 14 Javascript
jquery实现文本框的禁用和启用
Dec 07 Javascript
原生js实现放大镜效果
Jan 11 Javascript
JavaScript实现简单的树形菜单效果
Jun 23 Javascript
使用MUI框架模拟手机端的下拉刷新和上拉加载功能
Sep 04 Javascript
vue源码学习之Object.defineProperty 对数组监听
May 30 Javascript
vue-cli2打包前和打包后的css前缀不一致的问题解决
Aug 24 Javascript
利用Angular2的Observables实现交互控制的方法
Dec 27 Javascript
vue实现ToDoList简单实例
Feb 07 #Javascript
js Canvas绘制圆形时钟教程
Feb 06 #Javascript
JS中静态页面实现微信分享功能
Feb 06 #Javascript
JS日程管理插件FullCalendar中文说明文档
Feb 06 #Javascript
Javascript之深入浅出prototype
Feb 06 #Javascript
浅析jsopn跨域请求原理及cors(跨域资源共享)的完美解决方法
Feb 06 #Javascript
canvas实现图像布局填充功能
Feb 06 #Javascript
You might like
PHP自动生成月历代码
2006/10/09 PHP
PHP JSON格式数据交互实例代码详解
2011/01/13 PHP
PHP的一个完美GIF等比缩放类,附带去除缩放黑背景
2014/04/01 PHP
php压缩和解压缩字符串的方法
2015/03/14 PHP
PHP时间戳格式全部汇总 (获取时间、时间戳)
2016/06/13 PHP
javascript 强制刷新页面的实现代码
2009/12/13 Javascript
JavaScript 学习笔记(四)
2009/12/31 Javascript
JavaScript闭包详解
2015/02/02 Javascript
动态加载jQuery的方法
2015/06/16 Javascript
JS实现样式清新的横排下拉菜单效果
2015/10/09 Javascript
javascript适合移动端的日期时间拾取器
2015/11/10 Javascript
js验证框架之RealyEasy验证详解
2016/06/08 Javascript
用move.js库实现百叶窗特效
2017/02/08 Javascript
jQuery回调方法使用示例
2017/06/26 jQuery
详解vue-cli+es6引入es5写的js(两种方法)
2019/04/19 Javascript
js 动态校验开始结束时间的实现代码
2020/05/25 Javascript
vue实现五子棋游戏
2020/05/28 Javascript
如何在 ant 的table中实现图片的渲染操作
2020/10/28 Javascript
python生成日历实例解析
2014/08/21 Python
Python中AND、OR的一个使用小技巧
2015/02/18 Python
python根据时间生成mongodb的ObjectId的方法
2015/03/13 Python
python字符串过滤性能比较5种方法
2017/06/22 Python
Python结合ImageMagick实现多张图片合并为一个pdf文件的方法
2018/04/24 Python
Django中的cookie和session
2019/08/27 Python
python else语句在循环中的运用详解
2020/07/06 Python
python利用xlsxwriter模块 操作 Excel
2020/10/14 Python
animation和transition的区别
2020/10/12 HTML / CSS
美国网上眼镜供应商:LEOTONY(眼镜、RX太阳镜和太阳镜)
2017/10/31 全球购物
蔻驰法国官网:COACH法国
2018/11/14 全球购物
会计电算化专业毕业生自荐信
2013/12/20 职场文书
教师年度考核自我鉴定
2014/01/19 职场文书
自荐信的格式
2014/03/10 职场文书
教师师德师风个人整改方案
2014/09/18 职场文书
2015年妇委会工作总结
2015/05/22 职场文书
史上最全的军训拉歌口号
2015/12/25 职场文书
Mysql多层子查询示例代码(收藏夹案例)
2022/03/31 MySQL