栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

在JavaScript中逐个遍历数组

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

在JavaScript中逐个遍历数组

TL; DR

  • for-in
    除非你将其与防护措施配合使用,或者至少知道它为什么会咬住你,否则请勿使用。
  • 通常最好的选择是

    • 一个
      for-of
      环(ES2015 +只),
    • Array#forEach
      spec
      |
      MDN
      )(或其亲戚
      some
      等)(仅适用于ES5 +),
    • 一个简单的老式
      for
      循环,
    • for-in
      有保障。

但是,还有 很多 更多的探索,阅读…


Javascript具有用于遍历数组和类似数组的对象的强大语义。我将答案分为两部分:真正数组的选项,以及仅是数组之 类的
东西的选项,例如

arguments
对象,其他可迭代对象(ES2015 +),DOM集合等。

我会很快注意到,你 现在 可以通过将 ES2015 转换为ES5 ,甚至在ES5引擎上使用ES2015选项。搜索“ 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
及相关

在可以访问

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

MDN
):

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
将遵循这个顺序。(其他答案的详细信息。)

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

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

仅查看第一个示例:

for-in
如果使用适当的保护措施,则可以用来访问那些稀疏数组元素:

// `a` is a sparse arrayvar 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位无符号整数。)

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

// 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。使用迭代器最简单的方法是new

for-of
语句。看起来像这样:

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

在幕后,它从数组中获取一个 迭代器 并循环遍历,从而从中获取值。这没有使用

for-in
has
的问题,因为它使用了由对象(数组)定义的迭代器,并且数组定义了其迭代器遍历其 条目 (而不是其属性)。与
for-in
ES5
不同,条目的访问顺序是其索引的数字顺序。

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
如果是
false
value
则为可选,如果是,则为可选
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
。(在此答案的末尾,请参阅 警告,了解主机提供的对象 ,但这是一个罕见的问题。)

假设你要

forEach
Node
childNodes
属性上使用。你可以这样做:

    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`});
  1. 使用一个简单的
    for
    循环

显然,一个简单的

for
循环适用于类似数组的对象。

  1. 正确 使用
    for-in
    __

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

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

for-of
将使用对象提供的迭代器(如果有);我们将不得不看到它如何与各种数组状对象(尤其是主机提供的对象)一起使用。例如,
NodeList
from
的规范
querySelectorAll
已更新为支持迭代。对于该规范
HTMLCollection
getElementsByTagName
没有。

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

参见#4,我们必须看看迭代器如何发挥作用。

创建一个真实的数组

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

  1. 使用
    slice
    数组的方法

我们可以使用

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

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

因此,例如,如果我们要将a

NodeList
转换为真实数组,则可以执行以下操作:

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

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

this
像这样使用主机提供的对象。

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

还可以将ES2015的扩展语法与支持此功能的Javascript引擎一起使用:

    var trueArray = [...iterableObject];

因此,例如,如果我们想将a

NodeList
转换为真正的数组,使用扩展语法,这将变得非常简洁:

    var divs = [...document.querySelectorAll("div")];
  1. 使用
    Array.from
    (规格) | (MDN)

Array.from
(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]]
正确,但是测试很重要。)



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/383206.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号