飞码网-免费源码博客分享网站

点击这里给我发消息

在JavaScript中逐个遍历数组|-Javascript教程

飞码网-免费源码博客分享网站 爱上飞码网—https://www.codefrees.com— 飞码网-matlab-python-C++ 爱上飞码网—https://www.codefrees.com— 飞码网-免费源码博客分享网站

问:

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

我以为是这样的:

forEach(instance in theArray)

但这似乎是不正确的。

答:

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

现在可以通过将ES2015转换为ES5来使用ES2015选项,甚至在ES5引擎上也可以使用。搜索“ ES2015 transpiling” /“ ES6 transpiling”以了解更多...

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

对于实际数组

您目前在ECMAScript 5(“ ES5”)中拥有三个选项,这是目前最广泛支持的版本,在ECMAScript 2015中又添加了两个选项(“ ES2015”,“ ES6”):

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

细节:

1.使用forEach及相关

在任何可以访问ArrayES5(直接或使用polyfills)添加的功能的模糊现代环境(因此,不是IE8)中,都可以使用forEachspecMDN):

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

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

除非您支持IE8之类的过时浏览器(截至2016年9月,NetApps在该市场上所占份额刚刚超过4%),forEach否则您可以在没有垫片的情况下在通用网页中愉快地使用。如果确实需要支持陈旧的浏览器,forEach则填充/填充很容易完成(搜索“ es5 shim”以获得多个选项)。

forEach 这样做的好处是您不必在包含范围中声明索引和值变量,因为它们是作为迭代函数的参数提供的,因此可以很好地将作用域限定为该迭代。

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

此外,forEach它是“遍历所有对象”功能,但是ES5定义了其他几个有用的“遍历数组并做事”功能,包括:

  • every(在第一次回调返回false或出现错误时停止循环)
  • some(在第一次返回回调true或发生错误时停止循环)
  • filter(创建一个新数组,其中包含filter函数返回的元素,true并省略返回它的元素false
  • map (根据回调返回的值创建一个新数组)
  • reduce (通过重复调用回调并传递先前的值来建立一个值;有关详细信息,请参见规范;对汇总数组内容和许多其他内容很有用)
  • reduceRight(如reduce,但按降序而不是升序工作)

2.使用一个简单的for循环

有时,旧方法是最好的:

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

如果数组的长度将不会在循环过程中改变,它在性能敏感的代码(不可能),一个稍微复杂一点的版本抓住了长度达阵可能是一个很小的有点快:

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, 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)的引用:

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

3.正确使用for-in

您会听到有人告诉您使用它for-in,但这不是for-in目的。for-in遍历对象可枚举属性,而不是数组的索引。甚至在ES2015(ES6)中也不保证顺序。ES2015 +确实定义了对象属性的顺序(通过[[OwnPropertyKeys]][[Enumerate]]和使用它们的事物,例如Object.getOwnPropertyKeys),但并未定义for-in遵循该顺序的对象;不过,ES2020做到了。(其他答案的详细信息。)

for-in数组上唯一真正的用例是:

  • 这是一个稀疏的数组,里面有巨大的空隙,或者
  • 您正在使用非元素属性,并且希望将它们包括在循环中

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

// `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. 该对象具有该名称的自身属性(不是从其原型继承属性),并且

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

  3. 该键的值在被强制为数字时为<= 2 ^ 32-2(即4,294,967,294)。这个数字从哪里来?它是规范中数组索引定义的一部分。其他数字(非整数,负数,大于2 ^ 32-2的数字)不是数组索引。它的2 ^ 32的理由- 2是使得大于2 ^ 32下一个最大的索引值- 1,这是一个数组的最大值length可以有。(例如,数组的长度适合于32位无符号整数。)(向RobG表示支持,在我的博客文章的评论中指出我先前的测试不太正确。)

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

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

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

ES2015在JavaScript中添加了迭代器。使用迭代器最简单的方法是newfor-of语句。看起来像这样:

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

在幕后,它从数组中获取一个迭代器并循环遍历该数组,从而从中获取值。这没有使用for-inhas的问题,因为它使用了由对象(数组)定义的迭代器,并且数组定义了其迭代器遍历其条目(而不是其属性)。与for-inES5不同,访问条目的顺序是其索引的数字顺序。

5.明确使用迭代器(ES2015 +)

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

 

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

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

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

  • values():这是我上面使用的那个。它返回迭代,其中每个value是用于该迭代阵列条目("a""b",和"c"在实施例更早)。
  • keys():返回一个迭代器,其中每个迭代器value都是该迭代的关键(因此,对于我们a上面的代码,将是"0",然后是"1",然后是"2")。
  • entries():返回一个迭代器,其中每个迭代器value都是[key, value]该迭代形式的数组。

对于类似数组的对象

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

对数组使用上面的任何选项

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

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

    上的各种功能Array.prototype都是“有意通用的”,通常可以通过Function#call或在类似数组的对象上使用Function#apply。(在此答案的末尾,请参见警告以了解主机提供的对象,但这是一个罕见的问题。)

    假设您要forEachNodechildNodes属性上使用。您可以这样做:

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

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

    // (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. 使用一个简单的for循环

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

  3. 正确使用for-in

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

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

    for-of使用对象提供的迭代器(如果有)。这包括主机提供的对象。例如,NodeListfrom的规范querySelectorAll已更新以支持迭代。对于该规范HTMLCollectiongetElementsByTagName没有。

  5. 明确使用迭代器(ES2015 +)

    参见#4。

创建一个真实的数组

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

  1. 使用slice数组的方法

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

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

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

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

    请参阅下面的警告,了解主机提供的对象。特别要注意的是,这将在IE8及更早版本中失败,这不允许您this像这样使用主机提供的对象。

  2. 使用传播语法(...

    还可以将ES2015的扩展语法与支持该功能的JavaScript引擎一起使用。像一样for-of,它使用对象提供的迭代器(请参阅上一节的#4):

    var trueArray = [...iterableObject];
    

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

    var divs = [...document.querySelectorAll("div")];
    
  3. 采用 Array.from

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

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

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

    // 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函数与主机提供的类似数组的对象一起使用(DOM列表和浏览器而非JavaScript引擎提供的其他内容),则需要确保在目标环境中进行测试,以确保主机提供的对象行为正常。大多数(现在)确实表现正常,但是测试很重要。原因是Array.prototype您可能要使用的大多数方法都依赖于主机提供的对象,该对象为抽象[[HasProperty]]操作提供了诚实的答案。在撰写本文时,浏览器在这方面做得很好,但是5.1规范确实允许由主机提供的对象可能不诚实。在§8.6.2中,该部分开头附近的大表下方的几段中),其中表示:

除非另有说明,否则宿主对象可以以任何方式实现这些内部方法。例如,一种可能性是,[[Get]][[Put]]对特定宿主对象确实读取与存储的属性值但[[HasProperty]]总是产生

(我在ES2015规范中找不到等效的用法,但情况肯定仍然如此。)同样,在撰写本文时,现代浏览器中的常见宿主提供的类似数组的对象(NodeList例如,实例)可以处理[[HasProperty]]正确,但是测试很重要。)

飞码网-免费源码博客分享网站 爱上飞码网—https://www.codefrees.com— 飞码网-matlab-python-C++ 爱上飞码网—https://www.codefrees.com— 飞码网-免费源码博客分享网站
赞 ()
内容页底部广告位3
留言与评论(共有 0 条评论)
   
验证码: