首页 > 编程语言 >在JavaScript中遍历数组的循环(对于每个)

在JavaScript中遍历数组的循环(对于每个)

时间:2023-10-09 21:22:26浏览次数:57  
标签:遍历 const 迭代 JavaScript element forEach 数组 使用

内容来自 DOC https://q.houxu6.top/?s=在JavaScript中遍历数组的循环(对于每个)

我可以使用JavaScript遍历数组中的所有条目吗?


TL;DR

  • 你最好选择通常的方法是:

    • 使用 for-of 循环(ES2015+ 只支持;规范 | MDN) - 简单且适用于 async
    for (const element of theArray) {
        // ...使用 `element`...
    }
    
    • 使用 forEach(ES5+ 只支持;规范 | MDN) - 不适用于 async,但请查看详细信息。
    theArray.forEach(element => {
        // ...使用 `element`...
    });
    
    • 使用简单的旧式 for 循环 - 适用于 async
    for (let index = 0; index < theArray.length; ++index) {
        const element = theArray[index];
        // ...使用 `element`...
    }
    
    • (很少) 使用 for-in 并带有保护措施 - 适用于 async
    for (const propertyName in theArray) {
        if (/\*...是数组元素属性(请参见下文)...\*/) {
            const element = theArray[propertyName];
            // ...使用 `element`...
        }
    }
    
  • 一些快速“不要”:

    • 不要使用 for-in,除非你使用它带有保护措施或至少了解为什么它可能会伤害你。
    • 不要使用 map,如果你不打算使用它的返回值
      (不幸的是,有些人在教授 map [规范 / MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)],好像它是 forEach一样——但是正如我在博客上所写的,那不是它的用途。如果你不使用它创建的数组,就不要使用 map。)
    • 不要使用 forEach,如果回调执行异步工作并且你想要让 forEach等待该工作完成(因为它不会)。

但是还有更多可以探索的内容,请继续阅读...


JavaScript 对遍历数组和类似数组的对象具有强大的语义。我的回答分为两部分:适用于真正数组的选项,以及适用于只是类似数组的对象(例如 arguments 对象、其他可迭代对象(ES2015+)、DOM 集合等)的选项。

好的,让我们看一下我们的选项:

对于实际数组:

你有五个选项(两个基本上永远支持,一个是在 ECMAScript 5 中添加的 ["ES5"],另外两个是在 ECMAScript 2015 中添加的("ES2015",也称为 "ES6"):

  1. 使用 for-of(隐式使用迭代器)(ES2015+)
  2. 使用 forEach 及相关方法(ES5+)
  3. 使用简单的 for 循环
  4. 正确使用 for-in(ES2015+)
  5. 显式使用迭代器(ES2015+)

(你可以在这里查看这些旧规范:ES5ES2015,但两者都已被取代;当前编辑器草案始终在这里。)

细节:

1. 使用 for-of(隐式使用迭代器)(ES2015+)

ES2015 向 JavaScript 添加了 迭代协议和可迭代对象。数组是可迭代的(字符串、MapSet 也是如此,以及稍后你会看到的 DOM 集合和列表)。可迭代对象为其值提供迭代器。新的 for-of 语句通过迭代器循环返回迭代器返回的值。

const a = ["a", "b", "c"];
for (const element of a) { // 如果你喜欢,可以使用 `let` 代替 `const`
    console.log(element);
}
// a
// b
// c

没有比这更简单的了!在底层,这将从数组获取一个迭代器并循环通过迭代器返回的值。数组提供的迭代器提供数组元素的值,按顺序从开始到结束。
注意element在每次循环迭代中的作用域;在循环结束后尝试使用element会失败,因为它在循环体之外不存在。

从理论上讲,for-of循环涉及几次函数调用(一次获取迭代器,然后一次从迭代器中获取每个值)。即使这是真的,也不用担心,现代JavaScript引擎中的函数调用成本非常低(在我对forEach下面的关注之前,我一直很困扰;详细信息)(http://blog.niftysnippets.org/2012/02/foreach-and-runtime-cost.html)。但是此外,当处理诸如数组等本地迭代器的高性能代码时,JavaScript引擎会优化这些调用(将其消除)。

for-of完全支持async。如果您需要在循环体内按顺序完成工作(而不是并行),则可以在循环体中使用await等待Promise解决后再继续。以下是一个简单的示例:

显示代码片段

function delay(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

async function showSlowly(messages) {
    for (const message of messages) {
        await delay(400);
        console.log(message);
    }
}

showSlowly([
    "So", "long", "and", "thanks", "for", "all", "the", "fish!"
]);
// `.catch`省略,因为我们知道它永远不会拒绝

请注意每个单词在出现之前的延迟。

虽然只是编码风格的问题,但当遍历任何可迭代对象时,我首先会想到for-of

2. 使用forEach及相关方法

在任何即使是稍微现代的环境(因此,不是IE8)中,只要您可以访问由ES5添加的Array功能,就可以使用forEach规范 | MDN),如果您只处理同步代码(或者您不需要在循环期间等待异步过程完成):

const a = ["a", "b", "c"];
a.forEach((element) => {
    console.log(element);
});

forEach接受一个回调函数,并可选地接受一个值作为调用该回调时的this(在上面没有使用)。对于数组中的每个元素,回调函数按顺序被调用,跳过稀疏数组中不存在的元素。尽管在上面我只使用了一个参数,但回调函数是用三个参数调用的:该迭代的元素、该元素的索引和正在迭代的数组(以防您的函数还没有准备好)。

for-of一样,forEach的一个优点是您不必在包含作用域中声明索引和值变量;在这种情况下,它们作为迭代函数的参数提供,并且很好地限制在那个迭代中。

for-of不同,forEach的缺点是它不理解async函数和await。如果您将async函数用作回调,forEach不会等待该函数的promise解决后再继续。以下是使用forEach而不是for-ofasync示例——请注意初始延迟,但随后所有文本立即出现,而不是等待:

显示代码片段

function delay(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

async function showSlowly(messages) {
    // INCORRECT, doesn't wait before continuing,
    // doesn't handle promise rejections
    messages.forEach(async message => {
        await delay(400);
        console.log(message);
    });
}

showSlowly([
    "So", "long", "and", "thanks", "for", "all", "the", "fish!"
]);
// `.catch`省略,因为我们知道它永远不会拒绝

forEach是“遍历它们所有人”的函数,但ES5定义了其他几个有用的“从头到尾遍历数组并执行操作”的函数,包括:

  • everyspec | MDN) - 当回调函数返回假值时停止循环
  • somespec | MDN) - 当回调函数返回真值时停止循环
  • filterspec | MDN) - 创建一个新的数组,其中包含回调函数返回真值的元素,忽略那些不返回真值的元素
  • mapspec | MDN) - 从回调函数返回的值中创建一个新的数组
  • reducespec | MDN) - 通过反复调用回调函数并传递先前的值来构建一个值;有关详细信息,请参阅规范
  • reduceRightspec | MDN) - 类似于reduce,但以降序而不是升序工作

forEach一样,如果您将async函数作为回调函数使用,那么这些方法都不会等待函数的promise解决。这意味着:

  • 使用async函数回调与everysomefilter一起从来不合适,因为它们会将返回的promise视为真值;它们不会等待promise解决然后使用满足值。
  • 在目标是为了将数组转换为promises数组的情况下(例如传递给Promise组合器函数(Promise.allPromise.racepromise.allSettledPromise.any)时,使用async函数回调通常是合适的。
  • 使用async函数回调与reducereduceRight一起很少合适,因为(再次)回调函数总是会返回一个promise。但是有一种从使用reduce的数组构建一系列promise的惯用法(const promise = array.reduce((p, element) => p.then(/*...something using element...*/));),但通常情况下,在这些情况下,使用async函数的for循环或for循环会更清晰易懂,也更容易调试。
    在ES2015之前,循环变量必须存在于包含的作用域中,因为var只有函数级别的作用域,而不是块级作用域。但是正如你在上面的示例中所看到的,你可以在for循环中使用let来将变量范围限制在循环内。当你这样做时,每次循环迭代都会重新创建index变量,这意味着在循环体内创建的闭包会保留对该特定迭代的index的引用,从而解决了旧的“循环中的闭包”问题:

显示代码片段

// (从`querySelectorAll`获取的`NodeList`类似于数组)
const divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
    divs[index].addEventListener('click', e => {
        console.log("Index is: " + index);
    });
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>

在上面的代码中,如果你点击第一个并且点击最后一个,你会看到"Index is: 0"。如果你使用var而不是let(你总会看到"Index is: 5")。

for-of一样,for循环在async函数中也能很好地工作。以下是使用for循环的早期示例:

显示代码片段

function delay(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

async function showSlowly(messages) {
    for (let i = 0; i < messages.length; ++i) {
        const message = messages[i];
        await delay(400);
        console.log(message);
    }
}

showSlowly([
    "So", "long", "and", "thanks", "for", "all", "the", "fish!"
]);
// `.catch`省略了,因为我们知道他永远不会拒绝

4. 正确使用for-in

for-in不是用于遍历数组,而是用于遍历对象属性的名称。它确实经常看起来像是作为数组的副产品而起作用,但事实并非如此;它不仅仅是遍历数组索引,而是遍历所有可枚举的属性(包括继承的属性)。(它曾经的顺序也不是特定的;现在细节在这个其他答案中有说明,但是即使顺序现在被指定了,规则也很复杂,存在例外,依赖顺序并不是最佳实践。)

数组上for-in的唯一实际用例是:

  • 它是一个稀疏数组,其中有很大的间隙,或者
  • 你在数组对象上使用非元素属性,并且你想将它们包含在循环中

只看第一个示例:如果你使用适当的保护措施,你可以使用for-in访问这些稀疏数组元素:

// `a`是一个稀疏数组
const a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (const name in a) {
    if (Object.hasOwn(a, name) &&       // 这些检查是
        /^0$|^[1-9]\d\*$/.test(name) &&  // 下面解释的
        name <= 4294967294              // 
       ) {
        const element = a[name];
        console.log(a[name]);
    }
}

注意这三个检查:

  1. 对象拥有自己的属性名(不是从其原型继承的属性;这个检查通常也写作a.hasOwnProperty(name),但ES2022添加了Object.hasOwn,这可能更可靠),和
  2. 名称是所有十进制数字(例如,普通字符串形式,而不是科学计数法),和
  3. 当强制转换为数字时,名称的值应小于等于2^32 - 2(即4,294,967,294)。这个数字从哪里来?它是数组索引定义的一部分在规范中。其他数字(非整数、负数、大于2^32 - 2的数字)不是数组索引。之所以是2^32 - 2,是因为这让最大的索引值比2^32 - 1小1,而2^32 - 1是数组length可以具有的最大值。(例如,数组的长度适合32位无符号整数。)

...尽管这么说,大多数代码只做hasOwnProperty检查。

当然,你不会在内联代码中这样做。你会编写一个实用函数。也许:

显示代码片段

// 用于没有`forEach`的旧环境的工具函数
const hasOwn = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty);
const rexNum = /^0$|^[1-9]\d\*$/;
function sparseEach(array, callback, thisArg) {
    for (const name in array) {
        const index = +name;
        if (hasOwn(a, name) &&
            rexNum.test(name) &&
            index <= 4294967294
           ) {
            callback.call(thisArg, array[name], index, array);
        }
    }
}

const a = [];
a[5] = "five";
a[10] = "ten";
a[100000] = "one hundred thousand";
a.b = "bee";

sparseEach(a, (value, index) => {
    console.log("Value at " + index + " is " + value);
});

for一样,for-in在异步函数中工作得很好,如果它内部的工作需要按顺序进行。

显示代码片段

function delay(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

async function showSlowly(messages) {
    for (const name in messages) {
        if (messages.hasOwnProperty(name)) { // 人们通常只做这个检查
            const message = messages[name];
            await delay(400);
            console.log(message);
        }
    }
}

showSlowly([
    "So", "long", "and", "thanks", "for", "all", "the", "fish!"
]);
// `.catch`省略了,因为我们知道他永远不会拒绝

5. 显式使用迭代器(ES2015+)

for-of隐式使用迭代器,为你完成所有的繁重工作。有时候,你可能想要显式地使用迭代器。它看起来像这样:

const a = ["a", "b", "c"];
const it = a.values(); // 或者如果你喜欢的话,可以使用`const it = a[Symbol.iterator]();`
let entry;
while (!(entry = it.next()).done) {
    const element = entry.value;
    console.log(element);
}

迭代器是符合规范中的迭代器定义的对象。它的next方法每次调用时都会返回一个新的结果对象。结果对象有一个属性done,告诉我们是否完成,以及一个属性value,表示那次迭代的值。(如果donefalse,则value可选;如果valueundefined,则value可选。)

你得到的value取决于迭代器。在数组上,默认的迭代器提供了每个数组元素的值(在上面的例子中,是"a""b""c")。数组还有三个其他方法,它们返回迭代器:

  • values():这是[Symbol.iterator]方法的别名,返回默认的迭代器。
  • keys():返回一个提供数组中每个键(索引)的迭代器。在上面的例子中,它会提供"0",然后是"1",然后是"2"(是的,作为字符串)。
  • entries():返回一个提供[key, value]数组的迭代器。

由于迭代器对象在调用next之前不会前进,因此它们在async函数循环中表现良好。以下是之前使用显式迭代器的for-of示例:

function delay(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

async function showSlowly(messages) {
    const it = messages.values()
    while (!(entry = it.next()).done) {
        await delay(400);
        const element = entry.value;
        console.log(element);
    }
}

showSlowly([
    "So", "long", "and", "thanks", "for", "all", "the", "fish!"
]);
// `.catch` 省略了,因为我们知道它永远不会拒绝

对于类数组对象(Array-Like Objects)

除了真正的数组,还有一些具有length属性和全数字名称的属性的类数组对象,例如:NodeList实例,HTMLCollection实例,arguments对象等。我们如何遍历它们的内容?

使用上述选项中的大多数

至少有一些,可能全部或甚至大部分上述数组方法同样适用于类数组对象:

  1. 使用for-of(隐式使用迭代器)(ES2015+)

for-of使用对象提供的迭代器(如果有的话)。这包括由主机提供的对象(如DOM集合和列表)。例如,getElementsByXYZ方法的HTMLCollection实例和querySelectorAllNodeList实例都支持迭代。(这由HTML和DOM规范非常隐式地定义。基本上,任何具有length和索引访问的对象都自动可迭代。它需要被标记为可迭代;那只用于除了支持forEachvalueskeysentries方法之外还支持forEach的方法集合。NodeList支持;HTMLCollection不支持,但两者都是可迭代的。)

以下是遍历div元素的示例:

显示代码片段

const divs = document.querySelectorAll("div");
for (const div of divs) {
    div.textContent = Math.random();
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
  1. 使用forEach及相关方法(ES5+)

Array.prototype上的各种函数是“有意为之的通用”的,可以通过Function#callspec | MDN)或Function#applyspec | MDN)在类数组对象上使用。如果您必须处理IE8或更早版本(哎呀),请参阅此答案末尾的“宿主提供的对象注意事项”,但它不会影响模糊现代浏览器。

假设您想在NodechildNodes集合上使用forEach(由于是HTMLCollection,因此原生不支持forEach)。您会这样做:

Array.prototype.forEach.call(node.childNodes, (child) => {
    // 对`child`做一些操作
});

(请注意,不过,您也可以直接在node.childNodes上使用for-of。)

如果您要经常这样做,您可能需要将函数引用复制到变量中以供重用,例如:

// (所有这些可能都在模块或某个作用域函数中)
const forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);

// 然后稍后...
forEach(node.childNodes, (child) => {
    // 对`child`做一些操作
});
  1. 使用简单的for循环

显然,简单的for循环适用于类数组对象。

  1. 显式使用迭代器(ES2015+)

参见#1。

您可能可以逃脱使用for-in(带有保护措施),但是有了这些更合适的选项,没有理由尝试。

创建真实的数组

其他时候,你可能想要将类数组对象转换为真正的数组。这样做起来非常简单:

  1. 使用Array.from

Array.from (规格) | (MDN)(ES2015+,但可以轻松地实现polyfill)从类数组对象创建一个数组,可以选择首先通过映射函数传递条目。例如:

const divs = Array.from(document.querySelectorAll("div"));

这将从querySelectorAllNodeList中创建一个数组。

映射函数在你想要以某种方式映射内容时非常有用。例如,如果你想获取具有给定类的元素的标签名数组:

// 典型用法(带有箭头函数):
const divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);

// 传统函数(因为`Array.from`可以polyfill):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
    return element.tagName;
});
  1. 使用扩展语法(...

也可以使用ES2015的扩展语法[spread syntax]。像for-of一样,这个使用由对象提供的迭代器[iterator](参见上一节中的第1点):

const trueArray = [...iterableObject];

因此,例如,如果我们想将NodeList转换为真正的数组,使用扩展语法变得相当简洁:

const divs = [...document.querySelectorAll("div")];
  1. 使用数组的slice方法

我们可以使用数组的slice方法,它与其他提到的方法一样是“有意为之的通用”的,因此可以与类数组对象一起使用,如下所示:

const trueArray = Array.prototype.slice.call(arrayLikeObject);

因此,例如,如果我们想将NodeList转换为真正的数组,我们可以这样做:

const divs = Array.prototype.slice.call(document.querySelectorAll("div"));

(如果你仍然需要处理IE8 [哎呀],将会失败;IE8没有让你像这样使用主机提供的对象作为this。)

宿主提供的对象注意事项

如果你使用宿主提供的数组类似对象(例如,浏览器而不是JavaScript引擎提供的DOM集合等),像IE8这样的过时浏览器并不一定以那种方式处理,所以如果你必须支持它们,请确保在你的目标环境中进行测试。但对于模糊现代浏览器来说这不是问题。(对于非浏览器环境,自然取决于环境。)

标签:遍历,const,迭代,JavaScript,element,forEach,数组,使用
From: https://www.cnblogs.com/xiaomandujia/p/17753183.html

相关文章

  • 143-3 二叉树后序非递归遍历
    二叉树的后序非递归遍历使用辅助栈 r指针的作用是判断该结点是否遍历过#include<stdio.h>#include<stdlib.h>#defineMaxSize20typedefstructnode{intdata;structnode*lchild,*rchild;}TreeNode,*Tree;typedefTreeNode*Elem;typedefstruct{......
  • JS数组去重的4种办法
    有多种方法可以对数组进行去重,下面介绍的是常见的四种办法:使用Set数据结构:Set是ES6引入的一种新的数据结构,它存储唯一的值,可以利用它的去重特性来对数组进行去重。constarray=[1,2,2,3,3,4,5];constuniqueArray=[...newSet(array)];console.log(uniqueArr......
  • TypeScript与JavaScript比较(区别)
     TypeScript和JavaScript是目前项目开发中较为流行的两种脚本语言,TypeScript是JavaScript的一个超集,但是TypeScript与JavaScript之间又有什么样的区别呢?在选择开发语言时,又该如何抉择呢?本文将会深入对比这两种语言,讨论两种语言之间的关联和差异,并概述两种语言各自的优势......
  • 排序数组
       排序数组 数组C++JavaPython前言本题你可以选择直接调用库函数来对序列进行排序,但意义不大。由于排序算法有很多,本文只介绍三种常见的基于比较的复杂度较低的排序。方法一:快速排序思路和算法快速排序的主要思想是通过划分将待排序的序列分成前后两......
  • 利用 Javascript 生成数字序列
    <!DOCTYPEhtml><html><head><title>生成数字序列</title></head><body><h1>Element对象之innerHTML属性</h1><pid="demo"onclick="myFunction()">点击生成数字序列</p><script>funct......
  • Python生成随机整数数组的实用方法
    在编程中,生成随机整数数组是一项非常常见的任务。本文将介绍如何使用Python语言来生成随机整数数组,帮助读者掌握这一有用的编程技巧。通过实际的代码示例,我们将逐步指导读者完成生成随机整数数组的过程,并提供一些实际应用的建议。第一部分:了解随机数生成原理1.什么是随机数:-随机数......
  • vue中的循环遍历对象、数组和字符串
    vue循环遍历对象、数组和字符串1.循环遍历对象1.1vue在html里面循环遍历对象v-for="(val,key,i)indimItemMap":key="key" val-每一项key-key值i-第几个<el-table-columnprop="score"label="评分":show-overflow-tooltip="true"ali......
  • 寻找两个正序数组的中位数
    /* *@lcapp=leetcode.cnid=4lang=cpp *@lcprversion=21917 * *[4]寻找两个正序数组的中位数 *///@lccode=startclassSolution{public:  doublefindMedianSortedArrays(vector<int>&nums1,vector<int>&nums2){    if(nums1.size()......
  • jquery之each遍历
    jQuery的each和原生的JavaScript方法forEach原生的JavaScript方法forEach是EcmaScript5提供的不兼容IE8jQuery的each由jQuery这个第三方库提供jQuery2以下的版本是兼容IE8的它的each方法主要用来遍历jQuery实例对象(伪数组)同时它也可以作为......
  • 快慢指针用于数组的原地处理
    删除指定元素27.移除元素删除有序数组的重复项26.删除有序数组中的重复项删除有序数组重复项超过K次的部分80.删除有序数组中的重复项II整体来说,这类题目所用的方法都是快慢指针,只是其实现细节不尽相同而已。对我来说,做这种题目最好自己在纸上写写,不然很容......