【问题标题】:Currently, if we need to do reduce or forEach on iterable or iterator, would we just have to polyfill it?目前,如果我们需要对 iterable 或 iterator 执行 reduce 或 forEach,我们是否只需要对其进行 polyfill?
【发布时间】:2020-04-21 11:16:37
【问题描述】:

首先,对于可迭代对象和迭代器,是否有一些数组方法(例如 reduceforEach)有意义?是否真的要使用它们并且不想炸毁一个巨大的数组,我们现在只需要填充它们吗?

【问题讨论】:

  • 你可以有一个无限的迭代 - forEach 然后将永远执行。 总是有一个迭代方法有意义吗?
  • 是不是如果你做[...obj],那它已经可以是一个无限循环了?所以如果我们能做到[...obj],为什么不能obj.reduce()
  • [..obj] 类似于Array.from()。两者在构造一个实际的真实数组对象时都效率低下,该数组对象在内存中是一个单独的数据结构。对于大型集合,与仅迭代本机集合相比,这是非常低效的。如果您知道低效率对您的应用程序来说很好,那么您总是可以这样做,然后使用数组方法。但是,这与说语言应该隐含地为你做这件事是不同的。

标签: ecmascript-6 iterator iterable


【解决方案1】:

编辑

您的建议正在实施中。有一个 proposal at stage 2 of the TC39 process 用于向迭代器原型添加一大堆辅助方法(因此它们可以被集合使用),并且该提案包括您提到的两个 .forEach().reduce() 以及其他十几个。

我还没有完全理解这应该如何工作,因为规范谈到了迭代器助手,但随后显示在实际的 Set 实例上直接使用 .reduce(),就像在数组上使用它一样。所以,也许每个类都使用助手来实现他们自己的同名方法。由于您通常希望减少集合,而不是减少迭代器,因此这是有道理的。迭代器只是一个用于减少集合的工具,而不是集合本身。

他们redefine the .reduce() callback 只传递累加器和值(没有索引,没有对象)。仅供参考,我通过查看https://node.green/ 的末尾发现了这一点。因此,它正在开发中,由于有一个提议的标准,你可以填充它,你可以找到大量提议的新迭代器方法的示例实现here

这是提议的 Set.prototype.reduce()Map.prototype.reduce() 的 polyfill:

(function() {
if (!Set.prototype.reduce) {
    Object.defineProperty(Set.prototype, "reduce", {value: reduce});
}
if (!Map.prototype.reduce) {
    Object.defineProperty(Map.prototype, "reduce", {value: reduce});
}

function reduce(fn, initialValue) {
    if (typeof fn !== "function") {
        throw new TypeError("2nd argument to reduce must be function");
    }
    let noInitial = arguments.length < 2;
    let accumulator = initialValue;
    for (let [key, value] of this.entries()) {
        // if no initial value, get it from the first value
        if (noInitial) {
            accumulator = value;
            noInitial = false;
        } else {
            accumulator = fn(accumulator, key, value);
        }
    }
    // if there was nothing to iterate and initialValue was not passed
    // spec says this should be a TypeError
    if (noInitial) {
        throw new TypeError("iterable was empty and initalValue not passed")
    }
    return accumulator;
}

})();    

// demo code

let s = new Set([1,2,3,4,5,6]);

let sum = s.reduce((total, val) => {
return total += val;
}, 0);

console.log(`Set Total = ${sum}`);


let m = new Map([['one',1],['two',2],['three',3],['four',4]]);

let sum2 = m.reduce((total, key, val) => {
return total += val;
}, 0);

console.log(`Map Total = ${sum2}`);

我还没有完全弄清楚基于Iterator 类的.reduce() 方法是如何自动实现的,以便set.reduce()map.reduce() 将“正常工作”。我不确定它确实如此。我在想每个类仍然需要连接它自己的 .reduce() 方法,但它可以使用 Iterator 对象上的帮助器实现来做到这一点。也许这就是他们被称为“帮手”的原因。它们只是可用于连接您自己的顶级方法的常用函数。

它们可能仍然可以在迭代器上直接访问,但这似乎不是您通常使用它们的方式。


原答案...

您实际上并不需要forEach(),因为您可以在任何可迭代对象上使用for/of。所以,如果你真的想要forEach(),你必须自己实现它。我不会称它为 polyfill,因为没有您要填写的标准。因此,最好把它做成一个独立的函数,不要以非标准的方式污染原型。

如果您只是尝试迭代并从迭代中收集一些单个值,那么肯定有一些论据支持使用类似 reduce() 的函数与可迭代对象一起工作。同样,由于没有适用于所有可迭代对象的标准实现,因此您必须实现自己的函数以适用于任何可迭代对象。

为任意迭代实现reduce() 的一个问题是Array.prototype.reduce()index 传递给回调。这有点假设index 可以像数组一样访问。但是,一些具有可迭代性的集合不能通过索引访问。您仍然可以在迭代期间创建一个索引并将其作为一个计数器传递给回调,但它不一定能像在执行someArray.reduce() 时那样使用索引。

这是reduce() 的实现,适用于任何可迭代对象。作为参考,这里是 the spec for Array.prototype.reduce(),它适用于索引访问,而不适用于可迭代,这就是为什么它不能直接用于任何可迭代,但可以用于任何类似数组的对象。

let s = new Set([1,2,3,4,5,6]);

function reduce(iterable, fn, initialValue) {
    if (typeof fn !== "function") {
        throw new TypeError("2nd argument to reduce must be function");
    }
    let initialValuePresent = arguments.length >= 3;
    let accumulator = initialValue;
    let cntr= 0;
    for (let item of iterable) {
        // if no initial value, get it from the first value
        if (cntr === 0 && !initialValuePresent) {
            accumulator = item;
        } else {
            accumulator = fn(accumulator, item, cntr, iterable);
        }
        ++cntr;
    }
    // if there was nothing to iterate and initialValue was not passed
    // spec says this should be a TypeError
    if (cntr === 0 && !initialValuePresent) {
        throw new TypeError("iterable was empty and initalValue not passed")
    }
    return accumulator;
}

let sum = reduce(s, (total, item, cntr, obj) => {
    return total += item;
}, 0);

console.log(`Total = ${sum}`);

【讨论】:

  • for-of 不会给出索引,比如forEach((a, i) =&gt; {} 但我猜第三个参数不应该提供,因为我们可能不希望整个数组以
  • 最初,我认为reduce 对于可迭代对象来说应该没有那么复杂——为什么它没有进入 ES6 甚至 ES7 或 ES8 并且已经进入了 V8 和 Node
  • @nopole - 我们总是很难回答“为什么”有些事情没有完成。真正知道这一点的唯一方法是在那些决定花时间在哪些功能上的讨论中,或者找到讨论的记录,看看他们为什么决定他们决定什么。向可迭代对象的总体迁移是一个巨大的内部变化,影响了很多事情。从我自己的软件经验来看,我可以想象我想咬掉一大块可管理的工作,让它工作得非常好,然后看看人们认为未来最重要的事情是什么。
  • @nopole - .reduce().forEach() 一样是编程的便利,只需节省一点打字。人们总是可以通过常规的forfor/of 循环来完成您的.reduce() 目标,并通过循环跟踪您自己的累加器。因此,语言中的任何功能都不会因为没有任何可迭代的而被阻止。仅这一点就使它的优先级低于许多事情。但是,我真的不知道他们为什么选择不实施它。也许它提供的index 只是将它与一个可迭代的类数组对象关联太多。
  • @nopole - 请不要那样使用短语“polyfill”。 polyfill 是一种将已知的标准实现和规范添加到还没有它的旧版本代码的方法。 reduce() 在迭代器上没有标准实现。您正在讨论的不是polyfill。这是您要添加到某些迭代器的新的非标准功能。我个人不会这样做,因为一般做法往往会与未来的更改和与其他库的冲突等发生冲突……实际的 polyfill 是安全的,因为它是已知的标准实现。
猜你喜欢
  • 2012-09-26
  • 1970-01-01
  • 2021-12-19
  • 1970-01-01
  • 2021-02-22
  • 1970-01-01
  • 1970-01-01
  • 2022-12-05
  • 1970-01-01
相关资源
最近更新 更多