我们有多种方法来遍历 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)

语法

使用forfor/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/inforEach会跳过空元素,而forfor/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/inforEach会跳过空元素,数组中的空元素被称为"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

forfor/infor/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/inforEach()那么多奇怪的特例。for/of的缺点是我们取索引值不方便,而且不能这样链式调用forEach(). forEach()

使用for/of获取数组索引,可以这样写:

for (const [i, v] of arr.entries()) {
console.log(i, v);
}

参考

关于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的更多相关文章

  1. js几种数组遍历方法.

    第一种:普通的for循环 ; i < arr.length; i++) { } 这是最简单的一种遍历方法,也是使用的最多的一种,但是还能优化. 第二种:优化版for循环 ,len=arr.len ...

  2. JavaScript里面9种数组遍历!

    ​大家好,我在这里总结分享了JavaScript中的闹腾的数组循环家族. 1.大家最常用的for循环,我就不解释了: for(let i = 0; i < 5 ; i++){ console.l ...

  3. JavaScript 数组 遍历方法 map( ) 和 forEach( )

    let arr = [1, 3, 7, 6, 9]; 不用知道元素的个数,即不用设置开始下标和结束下标. 1:forEach( )会把数组中的每个值进行操作,没有返回值,undefined let j ...

  4. JavaScript 中的12种循环遍历方法

    原文:JavaScript 中的12种循环遍历方法 题目:请介绍 JavaScript 中有哪些循环和遍历的方法,说说它们的应用场景和优缺点? 1.for 循环 let arr = [1,2,3];f ...

  5. 浅谈6种JS数组遍历方法的区别

    本篇文章给大家介绍一下6种JS数组遍历方法:for.foreach.for in.for of.. each. ().each的区别.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. ...

  6. ES6 数组遍历方法的实战用法总结(forEach,every,some,map,filter,reduce,reduceRight,indexOf,lastIndexOf)

    目录 forEach every some map filter reduce && reduceRight indexOf lastIndexOf 前言 ES6原生语法中提供了非常多 ...

  7. js数组遍历方法总结

    数组遍历方法 1.for循环 使用临时变量,将长度缓存起来,避免重复获取数组长度,当数组较大时优化效果才会比较明显. 1 2 3 for(j = 0,len=arr.length; j < le ...

  8. JS数组遍历方法

    常用数组遍历方法: 1.原始for循环 var a = [1,2,3]; for(var i=0;i<a.length;i++){ console.log(a[i]); //结果依次为1,2,3 ...

  9. 数组遍历方法forEach 和 map 的区别

    数组遍历方法forEach 和 map 的区别:https://www.cnblogs.com/sticktong/p/7602783.html

随机推荐

  1. 支付宝2018年最新SDK对接验签的问题

    下单加签 AopUtils.SignAopRequest(sortedTxtParams,应用私钥, "UTF-8", false, "RSA2"); 异步回调 ...

  2. Ubuntu部署python3.7的开发和运行环境

    Ubuntu部署python3.7的开发和运行环境 1 概述 由于最近项目全部由python2.x转向 python3.x(使用 python3.7.1) ,之前的云主机的的默认python版本都面临 ...

  3. SQL Server 2017 新功能分享

    本篇文章是我在MVP直通车分享的关于SQL Server 2017的新功能,现在ppt分享如下,可以点击这里下载.

  4. js发送post请求,实现下载文件

    由于业务需求要下载文件的功能: <!DOCTYPE html> <html> <head> <meta charset="utf-8"&g ...

  5. CAS实现单点登录SSO执行原理及部署

    一.不落俗套的开始 1.背景介绍 单点登录:Single Sign On,简称SSO,SSO使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统. CAS框架:CAS(Centra ...

  6. 一文读懂四种常见的XML解析技术

    之前的文章我们讲解了<XML系列教程之Schema技术_上海尚学堂java培训技术干货><XML的概念.特点与作用.XML申明_上海Java培训技术干货>,大家可以点击回顾一下 ...

  7. [SQL]LeetCode577.员工奖金 | Employee Bonus

    Select all employee's name and bonus whose bonus is < 1000. Table:Employee +-------+--------+---- ...

  8. 用了 HTTPS 还不安全,问题就出在低版本 TLS 上

    HTTPS 加密时代已经来临,近两年,Google.Baidu.Facebook 等互联网巨头,不谋而合地开始大力推行 HTTPS, 2018 年 7 月 25 日,Chrome 68 上线,所有 H ...

  9. MVC从Controller到View的呈现

    图说MVC底层运行机制: 当路由机制已经激活Controller并InvokeAction后,如果返回的是View, 则ViewResult基于View呈现的请求响应机制内部借助MVC提供的View引 ...

  10. NMF学习练习:做电影推荐

    NMF是很久以前学的,基本快忘没了,昨天YX提出来一个关于NMF(同音同字不同义)的问题,才又想起来. 自己的学习笔记写的比较乱,好在网上资料多,摘了一篇,补充上自己笔记的内容,留此助记. NMF概念 ...