简体   繁体   English

JavaScript 无法迭代 Map 键或值

[英]JavaScript fails to iterate over Map keys or values

In certain scenarios an attempt to iterate over JavaScript Map keys or values fails.在某些情况下,尝试遍历 JavaScript Map 键或值会失败。 The script simply doesn't enter the loop.该脚本根本不进入循环。

function pushAndLog(array, values) {
    array.push(...values);
    for (const value of values) {
        console.log(value);
    }
}

const array = [];
const map = new Map([["a", 1], ["b", 2]]);
pushAndLog(array, map.keys());

In this example, array gets properly updated, but map keys don't get logged.在这个例子中, array得到正确更新,但映射键没有被记录。

Reason原因

This happens because Map.keys() and Map.values() methods return an Iterator instead of an Iterable object.这是因为Map.keys()Map.values()方法返回Iterator而不是Iterable对象。

How to fix怎么修

Avoid reusing iterators避免重复使用迭代器

As opposed to Iterable, Iterator is a stateful object.与 Iterable 不同,Iterator 是一个有状态的对象。 You can't reuse it.你不能重复使用它。 In particular, if you pass an Iterator to a function, you can't use it afterwards - you should create a new Iterator.特别是,如果您将一个 Iterator 传递给一个函数,之后就不能使用它——您应该创建一个新的 Iterator。 It is not always possible though.但这并不总是可能的。

Use iterables使用可迭代对象

The easiest way to fix this code is to pass a collection to the function instead of an iterator:修复此代码的最简单方法是将集合而不是迭代器传递给函数:

pushAndLog(array, [...map.keys()]);

But this solution is generally not the most efficient, as creation of a new collection consumes memory and CPU time.但这种解决方案通常不是最有效的,因为创建新集合会消耗内存和 CPU 时间。 There's a better solution.有一个更好的解决方案。 Add the following utilities to your code:将以下实用程序添加到您的代码中:

export function getIterableKeys<K, V>(map: Iterable<readonly [K, V]>): Iterable<K> {
    return {
        [Symbol.iterator]: function* () {
            for (const [key, _] of map) {
                yield key;
            }
        }
    };
}

export function getIterableValues<K, V>(map: Iterable<readonly [K, V]>): Iterable<V> {
    return {
        [Symbol.iterator]: function* () {
            for (const [_, value] of map) {
                yield value;
            }
        }
    };
}

Use them instead of native Map.keys() and Map.values() methods:使用它们代替本地 Map.keys() 和 Map.values() 方法:

pushAndLog(array, getIterableKeys(map));

It works better for bigger maps.它适用于更大的地图。

Notes:笔记:

  • The functions are written in TypeScript.这些函数是用 TypeScript 编写的。 To translate them to JavaScript, simply delete type definitions from function headers.要将它们转换为 JavaScript,只需从函数头中删除类型定义。
  • The functions are written as an ES6 module.这些函数被编写为 ES6 模块。 To translate them to plain JavaScript, simply delete export keywords.要将它们转换为纯 JavaScript,只需删除export关键字。
  • The nested functions are generator functions, ie they return a Generator which can also serve as Iterator.嵌套函数是生成器函数,即它们返回一个也可以用作迭代器的生成器。 In order to use this code, please make sure that your interpreter supports generators or use TypeScript/Babel transpilers in combination with regenerator-runtime library.为了使用此代码,请确保您的解释器支持生成器或将 TypeScript/Babel 转译器与regenerator-runtime库结合使用。

Some thoughts一些想法

Returning an Iterator object from Map.keys() and Map.values() methods is a bad design decision we have to live with and always keep in mind.从 Map.keys() 和 Map.values() 方法返回 Iterator 对象是一个糟糕的设计决策,我们必须接受并始终牢记。 Such errors are sometimes unexpected and very hard to track, as iterators serve well until you try to reuse them.此类错误有时是意料之外的并且很难跟踪,因为迭代器在您尝试重用它们之前可以很好地服务。 So you have to stay vigilant.所以你必须保持警惕。

In other mature programming languages such methods smartly return iterable objects:在其他成熟的编程语言中,此类方法巧妙地返回可迭代对象:

Both Java Iterable and .NET IEnumerable are reusable, so their users can't run into the same issue. Java Iterable 和 .NET IEnumerable 都是可重用的,因此它们的用户不会遇到相同的问题。 This approach is much safer.这种方法要安全得多。 It is a question for me why a relatively young JavaScript community invents their own solutions instead of inheriting best practices from existing mature programming languages.对我来说,为什么一个相对年轻的 JavaScript 社区发明了自己的解决方案,而不是从现有的成熟编程语言中继承最佳实践,这是一个问题。

Even though JavaScript Iterator works more or less like an Iterable object (you may use it in for..of loops, spread operators, collection constructors etc.), it formally breaks the Iterable class contract which is meant to be reusable, while Iterator is not.尽管 JavaScript Iterator 的工作方式或多或少像一个 Iterable 对象(您可以在 for..of 循环、扩展运算符、集合构造函数等中使用它),但它正式打破了旨在可重用的 Iterable 类契约,而 Iterator 是不是。

The problem is amplified by TypeScript definitions which pretend that Map.keys() and Map.values() return a so-called IterableIterator object extending Iterable interface, which is wrong for the same reasons.这个问题被 TypeScript 定义放大了,它们假装 Map.keys() 和 Map.values() 返回一个所谓的 IterableIterator 对象,扩展了 Iterable 接口,出于同样的原因,这是错误的。

Amazing Solutions!惊人的解决方案!

// 1.获取Map的keys
export function getIterableKeys<K, V>(map: Iterable<readonly [K, V]>): Iterable<K> {
    return {
        [Symbol.iterator]: function* () {
            for (const [key, _] of map) {
                yield key;
            }
        }
    };
}

// 2.获取key的values
export function getIterableValues<K, V>(map: Iterable<readonly [K, V]>): Iterable<V> {
    return {
        [Symbol.iterator]: function* () {
            for (const [_, value] of map) {
                yield value;
            }
        }
    };
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM