繁体   English   中英

JavaScript 无法迭代 Map 键或值

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

在某些情况下,尝试遍历 JavaScript Map 键或值会失败。 该脚本根本不进入循环。

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

在这个例子中, array得到正确更新,但映射键没有被记录。

原因

这是因为Map.keys()Map.values()方法返回Iterator而不是Iterable对象。

怎么修

避免重复使用迭代器

与 Iterable 不同,Iterator 是一个有状态的对象。 你不能重复使用它。 特别是,如果您将一个 Iterator 传递给一个函数,之后就不能使用它——您应该创建一个新的 Iterator。 但这并不总是可能的。

使用可迭代对象

修复此代码的最简单方法是将集合而不是迭代器传递给函数:

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

但这种解决方案通常不是最有效的,因为创建新集合会消耗内存和 CPU 时间。 有一个更好的解决方案。 将以下实用程序添加到您的代码中:

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

使用它们代替本地 Map.keys() 和 Map.values() 方法:

pushAndLog(array, getIterableKeys(map));

它适用于更大的地图。

笔记:

  • 这些函数是用 TypeScript 编写的。 要将它们转换为 JavaScript,只需从函数头中删除类型定义。
  • 这些函数被编写为 ES6 模块。 要将它们转换为纯 JavaScript,只需删除export关键字。
  • 嵌套函数是生成器函数,即它们返回一个也可以用作迭代器的生成器。 为了使用此代码,请确保您的解释器支持生成器或将 TypeScript/Babel 转译器与regenerator-runtime库结合使用。

一些想法

从 Map.keys() 和 Map.values() 方法返回 Iterator 对象是一个糟糕的设计决策,我们必须接受并始终牢记。 此类错误有时是意料之外的并且很难跟踪,因为迭代器在您尝试重用它们之前可以很好地服务。 所以你必须保持警惕。

在其他成熟的编程语言中,此类方法巧妙地返回可迭代对象:

Java Iterable 和 .NET IEnumerable 都是可重用的,因此它们的用户不会遇到相同的问题。 这种方法要安全得多。 对我来说,为什么一个相对年轻的 JavaScript 社区发明了自己的解决方案,而不是从现有的成熟编程语言中继承最佳实践,这是一个问题。

尽管 JavaScript Iterator 的工作方式或多或少像一个 Iterable 对象(您可以在 for..of 循环、扩展运算符、集合构造函数等中使用它),但它正式打破了旨在可重用的 Iterable 类契约,而 Iterator 是不是。

这个问题被 TypeScript 定义放大了,它们假装 Map.keys() 和 Map.values() 返回一个所谓的 IterableIterator 对象,扩展了 Iterable 接口,出于同样的原因,这是错误的。

惊人的解决方案!

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