JavaScript 的 4 种数组遍历方法: for VS forEach() VS for/in VS for/of
我们有多种方法来遍历 JavaScript 的数组或者对象,而它们之间的区别非常让人疑惑。Airbnb 编码风格禁止使用 for/in 与 for/of,你知道为什么吗?
这篇文章将详细介绍以下 4 种循环语法的区别:
for (let i = 0; i < arr.length; ++i)arr.forEach((v, i) => { /* ... */ })for (let i in arr)for (const v of arr)
语法
使用for和for/in,我们可以访问数组的下标,而不是实际的数组元素值:
for (let i = 0; i < arr.length; ++i) {
console.log(arr[i]);
}
for (let i in arr) {
console.log(arr[i]);
}
使用for/of,则可以直接访问数组的元素值:
for (const v of arr) {
console.log(v);
}
使用forEach(),则可以同时访问数组的下标与元素值:
arr.forEach((v, i) => console.log(v));
非数字属性
JavaScript 的数组就是 Object,这就意味着我们可以给数组添加字符串属性:
const arr = ["a", "b", "c"];
typeof arr; // 'object'
arr.test = "bad"; // 添加非数字属性
arr.test; // 'abc'
arr[1] === arr["1"]; // true, JavaScript数组只是特殊的Object
4 种循环语法,只有for/in不会忽略非数字属性:
const arr = ["a", "b", "c"];
arr.test = "bad";
for (let i in arr) {
console.log(arr[i]); // 打印"a, b, c, bad"
}
正因为如此,使用for/in遍历数组并不好。
其他 3 种循环语法,都会忽略非数字属性:
const arr = ["a", "b", "c"];
arr.test = "abc";
// 打印 "a, b, c"
for (let i = 0; i < arr.length; ++i) {
console.log(arr[i]);
}
// 打印 "a, b, c"
arr.forEach((el, i) => console.log(i, el));
// 打印 "a, b, c"
for (const el of arr) {
console.log(el);
}
要点: 避免使用for/in来遍历数组,除非你真的要想要遍历非数字属性。可以使用 ESLint 的guard-for-in规则来禁止使用for/in。
数组的空元素
JavaScript 数组可以有空元素。以下代码语法是正确的,且数组长度为 3:
const arr = ["a", , "c"];
arr.length; // 3
让人更加不解的一点是,循环语句处理['a',, 'c']与['a', undefined, 'c']的方式并不相同。
对于['a',, 'c'],for/in与forEach会跳过空元素,而for与for/of则不会跳过。
// 打印"a, undefined, c"
for (let i = 0; i < arr.length; ++i) {
console.log(arr[i]);
}
// 打印"a, c"
arr.forEach(v => console.log(v));
// 打印"a, c"
for (let i in arr) {
console.log(arr[i]);
}
// 打印"a, undefined, c"
for (const v of arr) {
console.log(v);
}
对于['a', undefined, 'c'],4 种循环语法一致,打印的都是"a, undefined, c"。
还有一种添加空元素的方式:
// 等价于`['a', 'b', 'c',, 'e']`
const arr = ["a", "b", "c"];
arr[5] = "e";
还有一点,JSON 也不支持空元素:
JSON.parse('{"arr":["a","b","c"]}');
// { arr: [ 'a', 'b', 'c' ] }
JSON.parse('{"arr":["a",null,"c"]}');
// { arr: [ 'a', null, 'c' ] }
JSON.parse('{"arr":["a",,"c"]}');
// SyntaxError: Unexpected token , in JSON at position 12
要点: for/in与forEach会跳过空元素,数组中的空元素被称为"holes"。如果你想避免这个问题,可以考虑禁用forEach:
parserOptions:
ecmaVersion: 2018
rules:
no-restricted-syntax:
- error
- selector: CallExpression[callee.property.name="forEach"]
message: Do not use `forEach()`, use `for/of` instead
函数的 this
for,for/in与for/of会保留外部作用域的this。
对于forEach, 除非使用箭头函数,它的回调函数的 this 将会变化。
使用 Node v11.8.0 测试下面的代码,结果如下:
"use strict";
const arr = ["a"];
arr.forEach(function() {
console.log(this); // 打印undefined
});
arr.forEach(() => {
console.log(this); // 打印{}
});
要点: 使用 ESLint 的no-arrow-callback规则要求所有回调函数必须使用箭头函数。
Async/Await 与 Generators
还有一点,forEach()不能与 Async/Await 及 Generators 很好的"合作"。
不能在forEach回调函数中使用 await:
async function run() {
const arr = ['a', 'b', 'c'];
arr.forEach(el => {
// SyntaxError
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(el);
});
}
不能在forEach回调函数中使用 yield:
function run() {
const arr = ['a', 'b', 'c'];
arr.forEach(el => {
// SyntaxError
yield new Promise(resolve => setTimeout(resolve, 1000));
console.log(el);
});
}
对于for/of来说,则没有这个问题:
async function asyncFn() {
const arr = ["a", "b", "c"];
for (const el of arr) {
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(el);
}
}
function* generatorFn() {
const arr = ["a", "b", "c"];
for (const el of arr) {
yield new Promise(resolve => setTimeout(resolve, 1000));
console.log(el);
}
}
当然,你如果将forEach()的回调函数定义为 async 函数就不会报错了,但是,如果你想让forEach按照顺序执行,则会比较头疼。
下面的代码会按照从大到小打印 0-9:
async function print(n) {
// 打印0之前等待1秒,打印1之前等待0.9秒
await new Promise(resolve => setTimeout(() => resolve(), 1000 - n * 100));
console.log(n);
}
async function test() {
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(print);
}
test();
要点: 尽量不要在forEach中使用 aysnc/await 以及 generators。
结论
简单地说,for/of是遍历数组最可靠的方式,它比for循环简洁,并且没有for/in和forEach()那么多奇怪的特例。for/of的缺点是我们取索引值不方便,而且不能这样链式调用forEach(). forEach()。
使用for/of获取数组索引,可以这样写:
for (const [i, v] of arr.entries()) {
console.log(i, v);
}
参考
- For-each over an array in JavaScript?
- Why is using “for…in” with array iteration a bad idea?
- Array iteration and holes in JavaScript
关于Fundebug
Fundebug专注于JS、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎大家免费试用!

版权声明
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2019/03/11/4-ways-to-loop-array-inj-javascript/
JavaScript 的 4 种数组遍历方法: for VS forEach() VS for/in VS for/of的更多相关文章
- js几种数组遍历方法.
第一种:普通的for循环 ; i < arr.length; i++) { } 这是最简单的一种遍历方法,也是使用的最多的一种,但是还能优化. 第二种:优化版for循环 ,len=arr.len ...
- JavaScript里面9种数组遍历!
大家好,我在这里总结分享了JavaScript中的闹腾的数组循环家族. 1.大家最常用的for循环,我就不解释了: for(let i = 0; i < 5 ; i++){ console.l ...
- JavaScript 数组 遍历方法 map( ) 和 forEach( )
let arr = [1, 3, 7, 6, 9]; 不用知道元素的个数,即不用设置开始下标和结束下标. 1:forEach( )会把数组中的每个值进行操作,没有返回值,undefined let j ...
- JavaScript 中的12种循环遍历方法
原文:JavaScript 中的12种循环遍历方法 题目:请介绍 JavaScript 中有哪些循环和遍历的方法,说说它们的应用场景和优缺点? 1.for 循环 let arr = [1,2,3];f ...
- 浅谈6种JS数组遍历方法的区别
本篇文章给大家介绍一下6种JS数组遍历方法:for.foreach.for in.for of.. each. ().each的区别.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. ...
- ES6 数组遍历方法的实战用法总结(forEach,every,some,map,filter,reduce,reduceRight,indexOf,lastIndexOf)
目录 forEach every some map filter reduce && reduceRight indexOf lastIndexOf 前言 ES6原生语法中提供了非常多 ...
- js数组遍历方法总结
数组遍历方法 1.for循环 使用临时变量,将长度缓存起来,避免重复获取数组长度,当数组较大时优化效果才会比较明显. 1 2 3 for(j = 0,len=arr.length; j < le ...
- JS数组遍历方法
常用数组遍历方法: 1.原始for循环 var a = [1,2,3]; for(var i=0;i<a.length;i++){ console.log(a[i]); //结果依次为1,2,3 ...
- 数组遍历方法forEach 和 map 的区别
数组遍历方法forEach 和 map 的区别:https://www.cnblogs.com/sticktong/p/7602783.html
随机推荐
- php 将一个或多个二维数组组合成一个二维数组并根据某个字段排序排序
最近再写项目的时候,碰到一个问题:如何将一个或多个二维数组组合成一个二维数组并根据某个字段排序排序:实在是想不到哪个php库中有哪个函数能实现,只能自己写一个了,将代码写出来后,发现自己的代码繁琐,并 ...
- 两层fragment嵌套时出现空白,(收藏别人的)
完美解决 两层Fragment,内层空白 转载:http://blog.csdn.net/bingospunky/article/details/51352400 目录(?)[+] 前言 两层Frag ...
- 链表加bfs求补图联通块
https://oj.neu.edu.cn/problem/1387 给一个点数N <= 100000, 边 <= 1000000的无向图,求补图的联通块数,以及每个块包含的点数 由于点数 ...
- Xaml Controls Gallery 的五个没有用的控件
HyperlinkButton 功能:这个控件可以实现点击按钮后跳到另一个按钮的功能. 我觉得这个功能有些多余,据我了解,一些深受欢迎的游戏大都具备的一个特点,那就是操作简单,界面中不会出现冗余的东西 ...
- js 生成随机炫彩背景
在浏览 https://ghost.org/xxxx/ 时. 可以使用 background-size: cover; 加上很小的像素图,放大后实现炫彩背景效果. 使用 js canvas 随机生成小 ...
- Android 音视频开发(七): 音视频录制流程总结
在前面我们学习和使用了AudioRecord.AudioTrack.Camera.MediaExtractor.MediaMuxer API.MediaCodec. 学习和使用了上述的API之后,相信 ...
- [Swift]LeetCode54. 螺旋矩阵 | Spiral Matrix
Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral or ...
- [Swift]LeetCode919. 完全二叉树插入器 | Complete Binary Tree Inserter
A complete binary tree is a binary tree in which every level, except possibly the last, is completel ...
- java中过多if-else分支语句的优化方案
利用Map优化过的的if-else分支 package com.taiping.test; import java.util.HashMap;import java.util.Map; public ...
- C#中为什么会出现空静态构造方法的写法
再过几个小时,就要回家过春节了,今天说些简单点的东西,大家在看C#代码的时候,一定会对这样的写法非常迷茫:在一个类中会出现一个空的静态构造方法.这不是多此一举吗,这样做的目的是什么?今天我就来说说这个 ...