在JavaScript中对数组进行逐遍 [英] For-each over an array in JavaScript

查看:75
本文介绍了在JavaScript中对数组进行逐遍的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何使用JavaScript遍历数组中的所有条目?

How can I loop through all the entries in an array using JavaScript?

我认为是这样的:

forEach(instance in theArray)

其中theArray是我的数组,但这似乎是不正确的.

Where theArray is my array, but this seems to be incorrect.

推荐答案

TL; DR

  • 除非您使用for-in进行安全防护,或者至少知道为什么它会咬您,否则请不要使用.
  • 您的最佳选择通常是

  • Don't use for-in unless you use it with safeguards or are at least aware of why it might bite you.
  • Your best bets are usually

  • for-of循环(仅适用于ES2015 +),
  • Array#forEach( spec | MDN )(或其亲戚some等) (仅适用于ES5 +)
  • 一个简单的老式for循环
  • for-in具有防护措施.
  • a for-of loop (ES2015+ only),
  • Array#forEach (spec | MDN) (or its relatives some and such) (ES5+ only),
  • a simple old-fashioned for loop,
  • or for-in with safeguards.

但是还有很多可供探索,请继续阅读...

But there's lots more to explore, read on...

JavaScript具有强大的语义,可以遍历数组和类似数组的对象.我将答案分为两部分:真正数组的选项,以及仅类似于数组 的东西的选项,例如arguments对象,其他可迭代对象(ES2015 +),DOM集合,依此类推.

JavaScript has powerful semantics for looping through arrays and array-like objects. I've split the answer into two parts: Options for genuine arrays, and options for things that are just array-like, such as the arguments object, other iterable objects (ES2015+), DOM collections, and so on.

我会很快注意到,即使将ES2015转换为ES5,也可以在ES5引擎上使用ES2015选项 now .搜索"ES2015 transpiling"/"ES6 transpiling"以了解更多...

I'll quickly note that you can use the ES2015 options now, even on ES5 engines, by transpiling ES2015 to ES5. Search for "ES2015 transpiling" / "ES6 transpiling" for more...

好的,让我们看看我们的选择:

Okay, let's look at our options:

ECMAScript  5 ("ES5")中,您有三个选择该版本目前得到最广泛的支持,并在 ECMAScript  2015 ("ES2015","ES6"):

You have three options in ECMAScript 5 ("ES5"), the version most broadly supported at the moment, and two more added in ECMAScript 2015 ("ES2015", "ES6"):

  1. 使用forEach及相关版本(ES5 +)
  2. 使用简单的for循环
  3. 正确使用for-in
  4. 使用for-of(隐式使用迭代器)(ES2015 +)
  5. 明确使用迭代器(ES2015 +)
  1. Use forEach and related (ES5+)
  2. Use a simple for loop
  3. Use for-in correctly
  4. Use for-of (use an iterator implicitly) (ES2015+)
  5. Use an iterator explicitly (ES2015+)

详细信息:

在任何模糊的现代环境(因此,不是IE8)中,您都可以使用ES5(直接或使用polyfills)添加的Array功能,可以使用forEach(

In any vaguely-modern environment (so, not IE8) where you have access to the Array features added by ES5 (directly or using polyfills), you can use forEach (spec | MDN):

var a = ["a", "b", "c"];
a.forEach(function(entry) {
    console.log(entry);
});

forEach接受回调函数,并且可以选择调用该回调时用作this的值(上面未使用).依次为数组中的每个条目调用回调,从而跳过稀疏数组中不存在的条目.尽管我在上面仅使用了一个参数,但回调函数使用以下三个参数调用:每个条目的值,该条目的索引以及对要迭代的数组的引用(以防您的函数尚未使用它) ).

forEach accepts a callback function and, optionally, a value to use as this when calling that callback (not used above). The callback is called for each entry in the array, in order, skipping non-existent entries in sparse arrays. Although I only used one argument above, the callback is called with three: The value of each entry, the index of that entry, and a reference to the array you're iterating over (in case your function doesn't already have it handy).

除非您支持IE8之类的过时浏览器(截至2016年9月,NetApps在该市场上所占份额刚刚超过4%),否则您可以在通用网页中愉快地使用forEach,而无需填充任何内容.如果您确实需要支持过时的浏览器,则可以轻松进行填充/填充forEach(搜索"es5 shim"以获得多个选项).

Unless you're supporting obsolete browsers like IE8 (which NetApps shows at just over 4% market share as of this writing in September 2016), you can happily use forEach in a general-purpose web page without a shim. If you do need to support obsolete browsers, shimming/polyfilling forEach is easily done (search for "es5 shim" for several options).

forEach的好处是您不必在包含范围中声明索引和值变量,因为它们是作为迭代函数的参数提供的,因此很好地范围仅限于该迭代.

forEach has the benefit that you don't have to declare indexing and value variables in the containing scope, as they're supplied as arguments to the iteration function, and so nicely scoped to just that iteration.

如果您担心为每个数组条目进行函数调用的运行时成本,请不必担心; 详细信息.

If you're worried about the runtime cost of making a function call for each array entry, don't be; details.

此外,forEach是遍历所有对象"功能,但是ES5定义了其他几个有用的遍历数组并执行操作"功能,包括:

Additionally, forEach is the "loop through them all" function, but ES5 defined several other useful "work your way through the array and do things" functions, including:

  • every (在第一次回调时停止循环返回false或错误的内容
  • some (在第一次回调时停止循环返回true或类似的内容
  • filter (创建一个新数组,其中包含以下元素:过滤器函数返回true,并忽略返回false的地方)
  • map (根据值创建一个新数组由回调返回)
  • reduce (通过反复调用来建立值回调,传入先前的值;有关详细信息,请参见规范;对汇总数组内容和许多其他内容很有用)
  • reduceRight (类似于reduce,但有效降序而不是升序)
  • every (stops looping the first time the callback returns false or something falsey)
  • some (stops looping the first time the callback returns true or something truthy)
  • filter (creates a new array including elements where the filter function returns true and omitting the ones where it returns false)
  • map (creates a new array from the values returned by the callback)
  • reduce (builds up a value by repeatedly calling the callback, passing in previous values; see the spec for the details; useful for summing the contents of an array and many other things)
  • reduceRight (like reduce, but works in descending rather than ascending order)

有时候旧的方法是最好的:

Sometimes the old ways are the best:

var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
    console.log(a[index]);
}

如果数组的长度在循环期间不会改变,并且是对性能敏感的代码(不太可能),那么抢占长度的稍微复杂一点的版本可能是 tiny 快一点:

If the length of the array won't change during the loop, and it's in performance-sensitive code (unlikely), a slightly more complicated version grabbing the length up front might be a tiny bit faster:

var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
    console.log(a[index]);
}

和/或向后计数:

var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
    console.log(a[index]);
}

但是使用现代JavaScript引擎,很少需要消耗掉最后的汁液.

But with modern JavaScript engines, it's rare you need to eke out that last bit of juice.

在ES2015及更高版本中,您可以将索引和值变量设置为for循环的本地变量:

In ES2015 and higher, you can make your index and value variables local to the for loop:

let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
    let value = a[index];
    console.log(index, value);
}
//console.log(index);   // would cause "ReferenceError: index is not defined"
//console.log(value);   // would cause "ReferenceError: value is not defined"

let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
    let value = a[index];
    console.log(index, value);
}
try {
    console.log(index);
} catch (e) {
    console.error(e);   // "ReferenceError: index is not defined"
}
try {
    console.log(value);
} catch (e) {
    console.error(e);   // "ReferenceError: value is not defined"
}

执行此操作时,对于每次循环迭代,不仅会重新创建value,还会重新创建index,这意味着在循环主体中创建的闭包保留对为此创建的index(和value)的引用.具体的迭代:

And when you do that, not just value but also index is recreated for each loop iteration, meaning closures created in the loop body keep a reference to the index (and value) created for that specific iteration:

let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
    divs[index].addEventListener('click', e => {
        console.log("Index is: " + index);
    });
}

let 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>

如果您有五个div,则单击第一个将获得索引为:0",如果单击最后一个则将为索引为:4".如果您使用var而不是let,则不起作用.

If you had five divs, you'd get "Index is: 0" if you clicked the first and "Index is: 4" if you clicked the last. This does not work if you use var instead of let.

您会看到有人告诉您使用for-in,但是这不是for-in的目的. for-in循环遍历对象的可枚举属性,而不遍历数组的索引. 不能保证订单,即使在ES2015(ES6)中也不能保证. ES2015 +确实定义了对象属性的顺序(通过 Object.getOwnPropertyKeys ),但它定义for-in将遵循该顺序. (其他答案中的详细信息.)

You'll get people telling you to use for-in, but that's not what for-in is for. for-in loops through the enumerable properties of an object, not the indexes of an array. The order is not guaranteed, not even in ES2015 (ES6). ES2015+ does define an order to object properties (via [[OwnPropertyKeys]], [[Enumerate]], and things that use them like Object.getOwnPropertyKeys), but it does not define that for-in will follow that order. (Details in this other answer.)

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

The only real use cases for for-in on an array are:

  • 这是一个 稀疏数组,具有大量强烈的差距,或者
  • 您正在使用非元素属性,并且希望将其包含在循环中
  • It's a sparse arrays with massive gaps in it, or
  • You're using non-element properties and you want to include them in the loop

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

Looking only at that first example: You can use for-in to visit those sparse array elements if you use appropriate safeguards:

// `a` is a sparse array
var key;
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (key in a) {
    if (a.hasOwnProperty(key)  &&        // These checks are
        /^0$|^[1-9]\d*$/.test(key) &&    // explained
        key <= 4294967294                // below
        ) {
        console.log(a[key]);
    }
}

请注意三项检查:

  1. 该对象具有该名称的 own 属性(不是从其原型继承的属性),并且

  1. That the object has its own property by that name (not one it inherits from its prototype), and

该键是所有十进制数字(例如,正常的字符串形式,而不是科学计数法),并且

That the key is all decimal digits (e.g., normal string form, not scientific notation), and

当键被强制为数字时,键的值是< = 2 ^ 32-2(即4,294,967,294).这个数字从哪里来?它是规范中数组索引 的定义的一部分.其他数字(非整数,负数,大于2 ^ 32-2的数字)不是数组索引.之所以是2 ^ 32- 2 ,是因为它使最大索引值比2 ^ 32- 1 低一个,而2 ^ 32- 1 是数组length可以达到的最大值有. (例如,数组的长度适合32位无符号整数.)(建议RobG在注释中指出

That the key's value when coerced to a number is <= 2^32 - 2 (which is 4,294,967,294). Where does that number come from? It's part of the definition of an array index in the specification. Other numbers (non-integers, negative numbers, numbers greater than 2^32 - 2) are not array indexes. The reason it's 2^32 - 2 is that that makes the greatest index value one lower than 2^32 - 1, which is the maximum value an array's length can have. (E.g., an array's length fits in a 32-bit unsigned integer.) (Props to RobG for pointing out in a comment on my blog post that my previous test wasn't quite right.)

当然,您不会在内联代码中执行此操作.您将编写一个实用程序函数.也许:

You wouldn't do that in inline code, of course. You'd write a utility function. Perhaps:

// Utility function for antiquated environments without `forEach`
var hasOwn = Object.prototype.hasOwnProperty;
var rexNum = /^0$|^[1-9]\d*$/;
function sparseEach(array, callback, thisArg) {
    var index;
    for (var key in array) {
        index = +key;
        if (hasOwn.call(a, key) &&
            rexNum.test(key) &&
            index <= 4294967294
            ) {
            callback.call(thisArg, array[key], index, array);
        }
    }
}

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

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

ES2015将 iterators 添加到JavaScript.使用迭代器的最简单方法是新的for-of语句.看起来像这样:

ES2015 adds iterators to JavaScript. The easiest way to use iterators is the new for-of statement. It looks like this:

const a = ["a", "b", "c"];
for (const val of a) {
    console.log(val);
}

在幕后,它从数组中获取一个 iterator 并循环遍历该数组,从而从中获取值.这不存在使用for-in的问题,因为它使用由对象(数组)定义的迭代器,并且数组定义其迭代器通过其 entries (而不是其属性)进行迭代. .与ES5中的for-in不同,条目的访问顺序是其索引的数字顺序.

Under the covers, that gets an iterator from the array and loops through it, getting the values from it. This doesn't have the issue that using for-in has, because it uses an iterator defined by the object (the array), and arrays define that their iterators iterate through their entries (not their properties). Unlike for-in in ES5, the order in which the entries are visited is the numeric order of their indexes.

有时候,您可能想显式地使用迭代器.您也可以这样做,尽管它比for-of笨拙得多.看起来像这样:

Sometimes, you might want to use an iterator explicitly. You can do that, too, although it's a lot clunkier than for-of. It looks like this:

const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
    console.log(entry.value);
}

迭代器是与规范中的迭代器定义匹配的对象.每次调用它的next方法都会返回一个新的 result对象.结果对象具有一个属性done,它告诉我们是否已完成,以及一个属性value,该值具有该迭代的值. (如果donefalse是可选的,valueundefined则是可选的.)

The iterator is an object matching the Iterator definition in the specification. Its next method returns a new result object each time you call it. The result object has a property, done, telling us whether it's done, and a property value with the value for that iteration. (done is optional if it would be false, value is optional if it would be undefined.)

value的含义因迭代器而异;数组至少支持三个返回迭代器的函数:

The meaning of value varies depending on the iterator; arrays support (at least) three functions that return iterators:

  • values():这是我上面使用的那个.它返回一个迭代器,其中每个value是该迭代的数组条目(在前面的示例中为"a""b""c").
  • keys():返回一个迭代器,其中每个value是该迭代的键(因此,对于我们上面的a而言,该变量将是"0",然后是"1",然后是"2").
  • entries():返回一个迭代器,其中每个value是该迭代的[key, value]形式的数组.
  • values(): This is the one I used above. It returns an iterator where each value is the array entry for that iteration ("a", "b", and "c" in the example earlier).
  • keys(): Returns an iterator where each value is the key for that iteration (so for our a above, that would be "0", then "1", then "2").
  • entries(): Returns an iterator where each value is an array in the form [key, value] for that iteration.

除了真正的数组之外,还有一些类似数组的对象,它们具有length属性和具有数字名称的属性:NodeList实例,arguments对象等.我们浏览它们的内容吗?

Aside from true arrays, there are also array-like objects that have a length property and properties with numeric names: NodeList instances, the arguments object, etc. How do we loop through their contents?

上面的至少一些(可能是大多数甚至全部)数组方法经常同样适用于类似数组的对象:

At least some, and possibly most or even all, of the array approaches above frequently apply equally well to array-like objects:

  1. 使用forEach及相关(ES5 +)

  1. Use forEach and related (ES5+)

Array.prototype上的各种功能是有意通用的",通常可以通过 . (请参见答案末尾的主机提供的对象注意事项,但这是一个罕见的问题.)

The various functions on Array.prototype are "intentionally generic" and can usually be used on array-like objects via Function#call or Function#apply. (See the Caveat for host-provided objects at the end of this answer, but it's a rare issue.)

假设您要在NodechildNodes属性上使用forEach.您可以这样做:

Suppose you wanted to use forEach on a Node's childNodes property. You'd do this:

Array.prototype.forEach.call(node.childNodes, function(child) {
    // Do something with `child`
});

如果要执行大量操作,则可能需要将函数引用的副本抓取到变量中以供重用,例如:

If you're going to do that a lot, you might want to grab a copy of the function reference into a variable for reuse, e.g.:

// (This is all presumably in some scoping function)
var forEach = Array.prototype.forEach;

// Then later...
forEach.call(node.childNodes, function(child) {
    // Do something with `child`
});

  • 使用简单的for循环

  • Use a simple for loop

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

    Obviously, a simple for loop applies to array-like objects.

    正确使用for-in

    Use for-in correctly

    for-in具有与数组相同的保护措施,也应该适用于类似数组的对象;上面#1中由主机提供的对象的警告可能适用.

    for-in with the same safeguards as with an array should work with array-like objects as well; the caveat for host-provided objects on #1 above may apply.

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

    Use for-of (use an iterator implicitly) (ES2015+)

    for-of将使用对象提供的迭代器(如果有);我们将不得不看一下它如何与各种类似数组的对象一起运行,尤其是主机提供的对象.例如,对querySelectorAllNodeList的规范进行了更新以支持迭代.不是来自getElementsByTagNameHTMLCollection规范.

    for-of will use the iterator provided by the object (if any); we'll have to see how this plays with the various array-like objects, particularly host-provided ones. For instance, the specification for the NodeList from querySelectorAll was updated to support iteration. The spec for the HTMLCollection from getElementsByTagName was not.

    显式使用迭代器(ES2015 +)

    请参阅#4,我们必须看看迭代器如何发挥作用.

    See #4, we'll have to see how iterators play out.

    创建一个真实的数组

    有时,您可能希望将类似数组的对象转换为真正的数组.做到这一点非常容易:

    Create a true array

    Other times, you may want to convert an array-like object into a true array. Doing that is surprisingly easy:

    1. 使用 slice 数组方法

    我们可以使用数组的slice方法,就像上面提到的其他方法一样,它是故意通用的",因此可以与类似数组的对象一起使用,如下所示:

    We can use the slice method of arrays, which like the other methods mentioned above is "intentionally generic" and so can be used with array-like objects, like this:

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

    因此,例如,如果我们要将NodeList转换为真实数组,则可以执行以下操作:

    So for instance, if we want to convert a NodeList into a true array, we could do this:

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

    请参阅下面的宿主提供的对象注意事项.特别要注意的是,这将在IE8及更早版本中失败,这不能让您像this这样使用主机提供的对象.

    See the Caveat for host-provided objects below. In particular, note that this will fail in IE8 and earlier, which don't let you use host-provided objects as this like that.

    使用传播语法( ...)

    还可以使用ES2015的传播语法具有支持此功能的JavaScript引擎:

    It's also possible to use ES2015's spread syntax with JavaScript engines that support this feature:

    var trueArray = [...iterableObject];
    

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

    So for instance, if we want to convert a NodeList into a true array, with spread syntax this becomes quite succinct:

    var divs = [...document.querySelectorAll("div")];
    

  • 使用Array.from (规范) | (MDN)

  • Use Array.from (spec) | (MDN)

    Array.from(ES2015 +,但很容易填充),从类似数组的对象创建一个数组,可以选择先将条目通过映射函数传递.所以:

    Array.from (ES2015+, but easily polyfilled) creates an array from an array-like object, optionally passing the entries through a mapping function first. So:

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

    或者,如果您想获取具有给定类的元素的标记名称的数组,则可以使用映射函数:

    Or if you wanted to get an array of the tag names of the elements with a given class, you'd use the mapping function:

    // Arrow function (ES2015):
    var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
    
    // Standard function (since `Array.from` can be shimmed):
    var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
        return element.tagName;
    });
    

  • 注意宿主提供的对象

    如果将Array.prototype函数与类似 host提供的数组对象(DOM列表和其他由浏览器而不是JavaScript引擎提供的东西)一起使用,则需要确保在您的目标环境,以确保主机提供的对象行为正常. 大多数行为正常(现在),但是进行测试很重要.原因是您可能要使用的大多数Array.prototype方法都依赖于主机提供的对象,该对象对抽象的§8.6.2中,以下几段该部分开头附近的大表),上面写着:

    Caveat for host-provided objects

    If you use Array.prototype functions with host-provided array-like objects (DOM lists and other things provided by the browser rather than the JavaScript engine), you need to be sure to test in your target environments to make sure the host-provided object behaves properly. Most do behave properly (now), but it's important to test. The reason is that most of the Array.prototype methods you're likely to want to use rely on the host-provided object giving an honest answer to the abstract [[HasProperty]] operation. As of this writing, browsers do a very good job of this, but the 5.1 spec did allow for the possibility a host-provided object may not be honest. It's in §8.6.2, several paragraphs below the big table near the beginning of that section), where it says:

    除非另有说明,否则

    主机对象可以以任何方式实现这些内部方法.例如,一种可能性是特定主机对象的[[Get]][[Put]]确实会获取并存储属性值,但是[[HasProperty]]始终会生成 false .

    Host objects may implement these internal methods in any manner unless specified otherwise; for example, one possibility is that [[Get]] and [[Put]] for a particular host object indeed fetch and store property values but [[HasProperty]] always generates false.

    (我在ES2015规范中找不到等效的术语,但情况一定会如此.)同样,在撰写本文时,现代浏览器[NodeList实例中,由主机提供的常见数组样对象,例如] 可以正确处理[[HasProperty]],但进行测试很重要.)

    (I couldn't find the equivalent verbiage in the ES2015 spec, but it's bound to still be the case.) Again, as of this writing the common host-provided array-like objects in modern browsers [NodeList instances, for instance] do handle [[HasProperty]] correctly, but it's important to test.)

    这篇关于在JavaScript中对数组进行逐遍的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆