问题

如何使用JavaScript循环访问数组中的所有对象?

我想是这样的:

forEach(instance in objects)

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



解决方法

TL; DR

  • 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

    • 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.

很多有待探索,请继续阅读...


JavaScript具有强大的语义,用于循环访问数组和数组类对象.我把答案分成两部分:真正数组的选项,以及只是数组的选项,如 arguments 对象,其他可迭代对象ES2015 +),DOM集合等.

我们很快就会注意到,您现在可以使用ES2015选项现在,即使在ES5引擎上,也可以通过转换 ES2015到ES5.搜索"ES2015 transpiling"/"ES6 transpiling"更多...

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

For Actual Arrays

您在 ECMAScript 5 ("ES5")中有三个选项,版本目前最受广泛支持,并且很快将在 ECMAScript 2015 < / a>("ES2015","ES6")是供应商正在支持的最新版本的JavaScript:

  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+)

详情:

1. Use forEach and related

如果您使用的环境支持ES5的 Array 功能(直接或使用垫片),您可以使用新的 forEach ( spec | MDN ):< / p>

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

forEach 接受一个迭代器函数,并且可选地,当调用该迭代器函数(上面未使用)时,可以使用一个值作为 this .对于数组中的每个条目,按顺序调用迭代器函数,跳过稀疏数组中不存在的条目.虽然我只使用上面的一个参数,迭代器函数调用三个:每个条目的值,该条目的索引,以及对您要迭代的数组的引用(如果你的函数还没有它方便).

除非你支持像IE8这样的过时的浏览器(NetApps在2016年9月的这篇文章中显示的市场份额只有4%以上),你可以在通用网络中愉快地使用 forEach 页面没有垫片.如果你确实需要支持过时的浏览器,可以轻松完成shim / polyfilling forEach (搜索"es5 shim"几个选项).

forEach 有一个好处,你不必在包含作用域中声明索引和值变量,因为它们作为迭代函数的参数提供,迭代.

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

另外, forEach 是"循环通过它们所有"功能,但ES5定义了几个其他有用的"通过数组和做事情"的功能,包括:

  • every (stops looping the first time the iterator returns false or something falsey)
  • some (stops looping the first time the iterator 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 iterator function)
  • reduce (builds up a value by repeated calling the iterator, 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)

2. Use a simple for loop

有时老方法是最好的:

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

如果数组的长度在循环期间不会改变,并且它在性能敏感的代码(不太可能),稍微更复杂的版本抓住前面的长度可能是 tiny < em> 位快:

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引擎,很少需要找出最后一点果汁.

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

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

当你这么做时,不仅仅是 value ,而且每个循环迭代都会重新创建 index ,意味着在循环体中创建的闭包会引用 index (和 value ):

let divs = Array.from(document.querySelector("div"));
for (let index = 0; index < divs.length; ++index) {
    div[index].addEventListener(e => {
        alert("Index is: " + index);
    });
}

如果你有五个div,如果你点击第一个和"索引:4",如果你点击最后一个,你会得到"索引是:0".如果您使用 var 而不是 let ,则

3. Use for-in correctly

您会得到人们告诉您使用 for-in ,但这不是 for-in 的用法. for-in 循环通过对象的可枚举属性,而不是数组的索引. 订单无法保证,甚至不在ES2015(ES6)中. ES2015确实定义了对象属性的顺序(通过 [[OwnPropertyKeys]] , [[Enumerate]] ,以及使用他们喜欢 Object.getOwnPropertyKeys ),但不会定义 for-in 会遵循该顺序. (详情请参阅此其他答案).

仍然,可以有用,特别是对于 稀疏 em>数组,如果您使用适当的保护措施:

// `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 are explained
        /^0$|^[1-9]\d*$/.test(key) &&    // and then hidden
        key <= 4294967294                // away below
        ) {
        console.log(a[key]);
    }
}

请注意两个检查:

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

  2. That the key is a base-10 numeric string in its normal string form and its value 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.)

这是对大多数数组的每个循环迭代增加的开销,但如果你有一个稀疏数组,它可以是一个更有效的方式循环,因为它只循环实际存在的条目.例如,对于上面的数组,我们循环总共三次(对于键"0","10""10000" - 记住,他们是字符串),而不是10,001次.

现在,你不会每次都写,所以你可以把它放在你的工具包:

function arrayHasOwnIndex(array, prop) {
    return array.hasOwnProperty(prop) && /^0$|^[1-9]\d*$/.test(prop) && prop <= 4294967294; // 2^32 - 2
}

然后我们会这样使用:

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

或者如果你只是对"大多数情况"测试感兴趣,你可以使用这个,但是当它接近,它是不正确的:

for (key in a) {
    // "Good enough" for most cases
    if (String(parseInt(key, 10)) === key && a.hasOwnProperty(key)) {
        console.log(a[key]);
    }
}

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

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

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

输出:

a
b
c

覆盖下,从数组中获取一个迭代器,循环遍历,从中获取值.这没有使用 for-in 具有的问题,因为它使用由对象(数组)定义的迭代器,并且数组定义它们的迭代器遍历它们的条目< em>(不是他们的属性).与ES5中的 for-in 不同,访问条目的顺序是它们的索引的数字顺序.

5. Use an iterator explicitly (ES2015+)

有时,您可能需要显式使用迭代器 .你也可以这样做,虽然它比 for-of 更复杂.它看起来像这样:

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

迭代器是每次调用 next 时都返回一个新对象的函数(具体来说,是一个生成器).迭代器返回的对象具有属性 done ,告诉我们它是否已经完成,以及一个属性 value 和该迭代的值.

value 的含义取决于迭代器;数组支持(至少)返回迭代器的三个函数:

  • values(): This is the one I used above. It returns an iterator where each value is the value for that iteration.
  • 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.

(撰写本文时,Firefox 29支持条目,但不支持

.)

For Array-Like Objects

除了真正的数组之外,还有具有 length 属性和数字名称属性的数组类对象: NodeList arguments 对象等.我们如何循环遍历他们的内容?

Use any of the options above for arrays

上面的数组方法中的至少一些,可能大部分或甚至全部经常同样适用于数组类对象:

  1. Use forEach and related (ES5+)

    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.)

    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`
    });
    
  2. Use a simple for loop

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

  3. Use for-in correctly

    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.

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

    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.

  5. Use an iterator explicitly (ES2015+)

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

Create a true array

其他时候,您可能想将类似数组的对象转换为真数组.这样做非常容易:

  1. Use the slice method of arrays

    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);
    

    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"));
    

    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.

  2. Use spread notation (...)

    It's also possible to use ES2015's spread notation (MDN currently calls it an operator; it isn't one), with JavaScript engines that support this feature:

    var trueArray = [...iterableObject];
    

    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")];
    
  3. Use Array.from (spec) | (MDN)

    Array.from (ES2015, but shimmable) 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;
    });
    

Caveat for host-provided objects

如果您使用主机提供的数组类对象(DOM列表和浏览器提供的其他东西,而不是JavaScript引擎)使用 Array.prototype 需要确保在目标环境中进行测试,以确保主机提供的对象正常运行. 大多数行为正确(现在),但测试非常重要.原因是大多数你可能想要使用的 Array.prototype 方法依赖于主机提供的对象给出抽象的真实答案 [[HasProperty]] 操作.截至本文,浏览器做得非常好,但ES5规范允许主机提供的对象可能不诚实;它位于§8.6.2(以下几段在该部分开头附近的大表),其中它表示:

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]] ,但测试非常重要.




相关问题推荐