专栏首页Vue开发社区4个Javascript 中的 for 循环

4个Javascript 中的 for 循环

来源 | https://blog.devgenius.io/four-ways-of-javascript-for-loop-c279ec4c0a10

翻译 | 杨小爱

在 ECMAScript5(简称 ES5)中,有三个循环。在 2015 年 6 月发布的 ECMAScript6(简称 ES6)中,新增了一种循环类型。他们是:

  • for
  • for in
  • for each
  • for of

今天,就让我们一起来看看这4个 for 循环。

1、简单的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]);
}

2、for-in

2.1、 使用 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

但这样做往往会产生问题。

2.2、 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遍历属性的顺序是不确定的,即输出结果的顺序与对象中属性的顺序无关,也与属性的字母顺序无关,也没有任何其他顺序。

2.3 、关于数组的真相

数组是Javascript中的一个对象,Array的索引是属性名。事实上,Javascript 中的“数组”有点误导。

Javascript 中的数组与大多数其他语言中的数组不同。首先,Javascript 中的数组在内存中不是连续的。

其次,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.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) &&
 /⁰$|^[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) &&
 /⁰$|^[1–9]\d*$/.test(prop) &&
 prop <= 4294967294; // 2³²-2
}

用法示例如下:

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

2.4、 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搜索每个属性相比,代码只关注给定的属性,节省了循环的开销和时间。

3、forEach

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

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

操作结果:


1
2
3

forEach 方法对数组中包含有效值的每一项执行一次回调函数,那些已经被删除(使用delete 方法等)或从未赋值的项将被跳过(不包括那些未定义的项) 或空值)。回调函数会依次传入三个参数:

  • 数组中当前项的值;
  • 当前项在数组中的索引;
  • 数组对象本身;

需要注意的是,forEach 遍历的范围会在第一次调用回调之前确定。调用 forEach 后添加到数组的项目不会被回调访问。

如果现有值发生变化,则传递给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”]

这里的索引是Number类型的,不会像for-in那样遍历原型链上的属性。

因此,在使用forEach时,我们不需要特别声明索引和要遍历的元素,因为这些都是作为回调函数的参数。

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

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

forEach 性能

您可以看看jsPerf。在不同浏览器下测试的结果是forEach没有for快。如果将测试代码放在控制台中,可能会得到不同的结果。主要原因是控制台的执行环境与真实的代码执行环境不同。

4、for-of

我们先来看一个例子:

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

运行的结果是:

a
b
c

为什么要引入for-of?

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

forEach 不能中断和返回;

for-in 的劣势更加明显。它不仅遍历数组中的元素,还遍历自定义属性,甚至访问原型链上的属性。此外,遍历数组元素的顺序可以是随机的。

所以,针对以上缺点,我们需要对原来的for循环进行改进。但是 ES6 不会破坏您已经编写的 JS 代码。

目前,数以千计的网站依赖于 for-in 循环,其中一些甚至将其用于数组遍历。通过修复 for-in 循环来添加数组遍历支持会使这一切变得更加混乱,因此标准委员会在 ES6 中添加了一个新的循环语法来解决当前的问题 for-of 。

那么 for-of 能做什么呢?

与forEach相比,它可以正确响应break、continue、return。

for-of 循环不仅支持数组,还支持大多数类似数组的对象,例如 DOM 节点列表对象。

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 有很多要讲的。我以后有时间写一篇关于它的文章。

文章分享自微信公众号:
前端开发社区

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

原始发表时间:2022-01-24
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 理解 JavaScript 中的循环

    所有这些表达式都有一个基本的功能:它们会重复一件事情直到一个具体的条件出现。在这篇文章中,我们将深入 for...of 表达式,去了解它是如何工作的,以及在我们...

    icepy
  • 如何检测JavaScript中的死循环?

    如果我们需要执行用户写的代码,如和避免死循环?我们最近遇到了这个问题,因为写错代码很常见,所以我们进行了一下尝试。

    JSer
  • JavaScript中如何中断forEach循环

    arr.forEach(callback[, thisArg]),callback会接收到三个参数:currentValue、index、array

    meteoric
  • 对于 JavaScript 中循环之间的技术差异概述

    在 JavaScript 中使用循环时,需要理解两个关键点:可枚举的属性和可迭代的对象。

    前端小智@大迁世界
  • 对于 JavaScript 中循环之间的技术差异概述

    可枚举对象的一个定义特征是,当通过赋值操作符向对象分配属性时,我们将内部 enumerable 标志设置为true,这是默认值。

    前端小智@大迁世界
  • JavaScript的循环语句

    for循环是一种前测试循环语句,但它具有在执行循环之前初始化变量和定义循环后要执行的代码的能力。以下是for循环的语法:

    小小鱼儿小小林
  • 在 JavaScript 中优雅的提取循环内的数据 [每日前端夜话0x2D]

    从 A 行开始的循环用来记录文件路径。它是 for-of 循环和递归的组合(递归调用在 B 行)。

    疯狂的技术宅
  • 深入浅出 JavaScript 中的For循环之详解

    今天我想分享一个有关于循环筛选的知识点,也许是前端小白的你首先想到的是用for循环做筛选,但我这种小菜鸟想到的就是map(工作中很喜欢用= =),学过数据结构的...

    苏南
  • 在chromev8中的JavaScript事件循环分析

    JavaScript从诞生之日起就是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互。

    Yerik
  • JavaScript 模块的循环加载

    "循环加载"(circular dependency)指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。 // a.js var b = requir...

    ruanyf
  • 前端 JavaScript 中的三种 for 循环语句总结

    JavaScript 中的 for 循环语句相信大家都已经快用厌了,现在有好多文章都在讲怎么减少代码中的 for 循环语句,但是,你又不得不承认它们真的很有用。...

    编程三昧
  • Javascript For循环中的重难点

    如果大家有过Python的基础,一定知道python中的for循环。同理,javascript是Web的编程语言,所以javascript中也存在for循环。并...

    算法与编程之美
  • 写出高效的Javascript循环语句

    当涉及到循环性能时,争论始终是关于使用哪个循环。哪个是最快,最高效的?事实是,在JavaScript提供的四种循环类型中,只有一种比for-in循环要慢得多。循...

    前端修罗场
  • 回到基础:优化 JavaScript 的循环[每日前端夜话0x52]

    翻译:疯狂的技术宅 原文:https://medium.freecodecamp.org/how-to-optimize-your-javascript-app...

    疯狂的技术宅
  • JavaScript 中哪一种循环最快呢?

    最让我感到惊讶的事情是,当我在本地计算机上进行测试之后,我不得不接受 for(倒序)是所有 for 循环中最快的这一事实。下面我会举个对一个包含超过一百万项元素...

    coder_koala
  • ModelBuilder中的For循环和While循环

    现在开始讲迭代器,迭代是指以一定的自动化程度多次重复某个过程,通常又称为循环。说的通俗点就是批量循环处理,简称批处理。

    陈南GISer
  • ModelBuilder中的For循环和While循环

    现在开始讲迭代器,迭代是指以一定的自动化程度多次重复某个过程,通常又称为循环。说的通俗点就是批量循环处理,简称批处理。

    陈南GISer
  • Python中的循环(或循环)导入

    本文翻译自:Circular (or cyclic) imports in Python

    用户7886150

扫码关注腾讯云开发者

领取腾讯云代金券