我试图使用新的地图对象从Javascript EC6,因为它已经支持在最新的Firefox和Chrome版本。

但我发现它在“函数式”编程中非常有限,因为它缺乏经典的映射、过滤器等方法,这些方法可以很好地处理[key, value]对。它有一个forEach,但不返回回调结果。

如果我可以将它的map.entries()从一个MapIterator转换成一个简单的数组,那么我就可以使用标准的.map, .filter而不需要额外的hack。

是否有一个“好”的方法将Javascript迭代器转换为数组? 在python中,这就像执行list(iterator)…但是数组(m.entries())返回一个以迭代器为第一个元素的数组!!

EDIT

我忘记指定我正在寻找一个答案,无论地图工作,这意味着至少Chrome和Firefox(数组.from不工作在Chrome)。

PS.

我知道有很棒的wu.js,但它对traceur的依赖让我很反感……


当前回答

没有必要将Map转换为Array。你可以简单地为map对象创建map和filter函数:

function map(functor, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self);

    return result;
}

function filter(predicate, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self);

    return result;
}

例如,您可以附加一个bang(即!字符)到键为原语的映射的每个条目的值。

var object = new Map; object.set("", "empty string"); object.set(0, "number zero"); object.set(object, "itself"); var result = map(appendBang, filter(primitive, object)); alert(result.get("")); // empty string! alert(result.get(0)); // number zero! alert(result.get(object)); // undefined function primitive(value, key) { return isPrimitive(key); } function appendBang(value) { return value + "!"; } function isPrimitive(value) { var type = typeof value; return value === null || type !== "object" && type !== "function"; } <script> function map(functor, object, self) { var result = new Map; object.forEach(function (value, key, object) { result.set(key, functor.call(this, value, key, object)); }, self || null); return result; } function filter(predicate, object, self) { var result = new Map; object.forEach(function (value, key, object) { if (predicate.call(this, value, key, object)) result.set(key, value); }, self || null); return result; } </script>

您还可以在map上添加映射和筛选方法。原型,让它读起来更好。虽然通常不建议修改本机原型,但我认为map .prototype的map和filter可能是个例外:

var object = new Map; object.set("", "empty string"); object.set(0, "number zero"); object.set(object, "itself"); var result = object.filter(primitive).map(appendBang); alert(result.get("")); // empty string! alert(result.get(0)); // number zero! alert(result.get(object)); // undefined function primitive(value, key) { return isPrimitive(key); } function appendBang(value) { return value + "!"; } function isPrimitive(value) { var type = typeof value; return value === null || type !== "object" && type !== "function"; } <script> Map.prototype.map = function (functor, self) { var result = new Map; this.forEach(function (value, key, object) { result.set(key, functor.call(this, value, key, object)); }, self || null); return result; }; Map.prototype.filter = function (predicate, self) { var result = new Map; this.forEach(function (value, key, object) { if (predicate.call(this, value, key, object)) result.set(key, value); }, self || null); return result; }; </script>


编辑:在Bergi的回答中,他为所有可迭代对象创建了通用映射和过滤器生成器函数。使用它们的好处是,由于它们是生成器函数,它们不分配中间可迭代对象。

例如,上面定义的map和filter函数创建了新的map对象。因此调用object.filter(primitive).map(appendBang)会创建两个新的Map对象:

var intermediate = object.filter(primitive);
var result = intermediate.map(appendBang);

创建中间可迭代对象的开销很大。Bergi的生成器函数解决了这个问题。它们不分配中间对象,但允许一个迭代器将其值惰性地提供给下一个迭代器。这种优化在函数式编程语言中被称为融合或毁林,它可以显著提高程序性能。

Bergi的生成器函数唯一的问题是它们不是特定于Map对象的。相反,它们对所有可迭代对象都是通用的。因此,它不是使用(value, key)对调用回调函数(正如我在映射Map时所期望的那样),而是使用(value, index)对调用回调函数。否则,它是一个很好的解决方案,我肯定会推荐使用它而不是我提供的解决方案。

这些是我用于映射和过滤Map对象的特定生成器函数:

function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}

它们的用法如下:

var object = new Map; object.set("", "empty string"); object.set(0, "number zero"); object.set(object, "itself"); var result = toMap(map(appendBang, filter(primitive, object.entries()))); alert(result.get("")); // empty string! alert(result.get(0)); // number zero! alert(result.get(object)); // undefined var array = toArray(map(appendBang, filter(primitive, object.entries()))); alert(JSON.stringify(array, null, 4)); function primitive(value, key) { return isPrimitive(key); } function appendBang(value) { return value + "!"; } function isPrimitive(value) { var type = typeof value; return value === null || type !== "object" && type !== "function"; } <script> function * map(functor, entries, self) { var that = self || null; for (var entry of entries) { var key = entry[0]; var value = entry[1]; yield [key, functor.call(that, value, key, entries)]; } } function * filter(predicate, entries, self) { var that = self || null; for (var entry of entries) { var key = entry[0]; var value = entry[1]; if (predicate.call(that, value, key, entries)) yield [key, value]; } } function toMap(entries) { var result = new Map; for (var entry of entries) { var key = entry[0]; var value = entry[1]; result.set(key, value); } return result; } function toArray(entries) { var array = []; for (var entry of entries) { array.push(entry[1]); } return array; } </script>

如果你想要一个更流畅的界面,你可以这样做:

var object = new Map; object.set("", "empty string"); object.set(0, "number zero"); object.set(object, "itself"); var result = new MapEntries(object).filter(primitive).map(appendBang).toMap(); alert(result.get("")); // empty string! alert(result.get(0)); // number zero! alert(result.get(object)); // undefined var array = new MapEntries(object).filter(primitive).map(appendBang).toArray(); alert(JSON.stringify(array, null, 4)); function primitive(value, key) { return isPrimitive(key); } function appendBang(value) { return value + "!"; } function isPrimitive(value) { var type = typeof value; return value === null || type !== "object" && type !== "function"; } <script> MapEntries.prototype = { constructor: MapEntries, map: function (functor, self) { return new MapEntries(map(functor, this.entries, self), true); }, filter: function (predicate, self) { return new MapEntries(filter(predicate, this.entries, self), true); }, toMap: function () { return toMap(this.entries); }, toArray: function () { return toArray(this.entries); } }; function MapEntries(map, entries) { this.entries = entries ? map : map.entries(); } function * map(functor, entries, self) { var that = self || null; for (var entry of entries) { var key = entry[0]; var value = entry[1]; yield [key, functor.call(that, value, key, entries)]; } } function * filter(predicate, entries, self) { var that = self || null; for (var entry of entries) { var key = entry[0]; var value = entry[1]; if (predicate.call(that, value, key, entries)) yield [key, value]; } } function toMap(entries) { var result = new Map; for (var entry of entries) { var key = entry[0]; var value = entry[1]; result.set(key, value); } return result; } function toArray(entries) { var array = []; for (var entry of entries) { array.push(entry[1]); } return array; } </script>

希望这能有所帮助。

其他回答

2019年的一个小更新:

现在array. from似乎是普遍可用的,而且,它接受第二个参数mapFn,这阻止了它创建一个中间数组。基本上是这样的:

Array.from(myMap.entries(), entry => {...});

你可以使用像https://www.npmjs.com/package/itiriri这样的库,为可迭代对象实现类似数组的方法:

import { query } from 'itiriri';

const map = new Map();
map.set(1, 'Alice');
map.set(2, 'Bob');

const result = query(map)
  .filter([k, v] => v.indexOf('A') >= 0)
  .map([k, v] => `k - ${v.toUpperCase()}`);

for (const r of result) {
  console.log(r); // prints: 1 - ALICE
}

你正在寻找新的array .from函数,它将任意可迭代对象转换为数组实例:

var arr = Array.from(map.entries());

现在支持Edge, FF, Chrome和Node 4+。

当然,直接在迭代器接口上定义map、filter和类似的方法可能是值得的,这样就可以避免分配数组。你可能还想使用生成器函数而不是高阶函数:

function* map(iterable) {
    var i = 0;
    for (var item of iterable)
        yield yourTransformation(item, i++);
}
function* filter(iterable) {
    var i = 0;
    for (var item of iterable)
        if (yourPredicate(item, i++))
             yield item;
}

没有必要将Map转换为Array。你可以简单地为map对象创建map和filter函数:

function map(functor, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self);

    return result;
}

function filter(predicate, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self);

    return result;
}

例如,您可以附加一个bang(即!字符)到键为原语的映射的每个条目的值。

var object = new Map; object.set("", "empty string"); object.set(0, "number zero"); object.set(object, "itself"); var result = map(appendBang, filter(primitive, object)); alert(result.get("")); // empty string! alert(result.get(0)); // number zero! alert(result.get(object)); // undefined function primitive(value, key) { return isPrimitive(key); } function appendBang(value) { return value + "!"; } function isPrimitive(value) { var type = typeof value; return value === null || type !== "object" && type !== "function"; } <script> function map(functor, object, self) { var result = new Map; object.forEach(function (value, key, object) { result.set(key, functor.call(this, value, key, object)); }, self || null); return result; } function filter(predicate, object, self) { var result = new Map; object.forEach(function (value, key, object) { if (predicate.call(this, value, key, object)) result.set(key, value); }, self || null); return result; } </script>

您还可以在map上添加映射和筛选方法。原型,让它读起来更好。虽然通常不建议修改本机原型,但我认为map .prototype的map和filter可能是个例外:

var object = new Map; object.set("", "empty string"); object.set(0, "number zero"); object.set(object, "itself"); var result = object.filter(primitive).map(appendBang); alert(result.get("")); // empty string! alert(result.get(0)); // number zero! alert(result.get(object)); // undefined function primitive(value, key) { return isPrimitive(key); } function appendBang(value) { return value + "!"; } function isPrimitive(value) { var type = typeof value; return value === null || type !== "object" && type !== "function"; } <script> Map.prototype.map = function (functor, self) { var result = new Map; this.forEach(function (value, key, object) { result.set(key, functor.call(this, value, key, object)); }, self || null); return result; }; Map.prototype.filter = function (predicate, self) { var result = new Map; this.forEach(function (value, key, object) { if (predicate.call(this, value, key, object)) result.set(key, value); }, self || null); return result; }; </script>


编辑:在Bergi的回答中,他为所有可迭代对象创建了通用映射和过滤器生成器函数。使用它们的好处是,由于它们是生成器函数,它们不分配中间可迭代对象。

例如,上面定义的map和filter函数创建了新的map对象。因此调用object.filter(primitive).map(appendBang)会创建两个新的Map对象:

var intermediate = object.filter(primitive);
var result = intermediate.map(appendBang);

创建中间可迭代对象的开销很大。Bergi的生成器函数解决了这个问题。它们不分配中间对象,但允许一个迭代器将其值惰性地提供给下一个迭代器。这种优化在函数式编程语言中被称为融合或毁林,它可以显著提高程序性能。

Bergi的生成器函数唯一的问题是它们不是特定于Map对象的。相反,它们对所有可迭代对象都是通用的。因此,它不是使用(value, key)对调用回调函数(正如我在映射Map时所期望的那样),而是使用(value, index)对调用回调函数。否则,它是一个很好的解决方案,我肯定会推荐使用它而不是我提供的解决方案。

这些是我用于映射和过滤Map对象的特定生成器函数:

function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}

它们的用法如下:

var object = new Map; object.set("", "empty string"); object.set(0, "number zero"); object.set(object, "itself"); var result = toMap(map(appendBang, filter(primitive, object.entries()))); alert(result.get("")); // empty string! alert(result.get(0)); // number zero! alert(result.get(object)); // undefined var array = toArray(map(appendBang, filter(primitive, object.entries()))); alert(JSON.stringify(array, null, 4)); function primitive(value, key) { return isPrimitive(key); } function appendBang(value) { return value + "!"; } function isPrimitive(value) { var type = typeof value; return value === null || type !== "object" && type !== "function"; } <script> function * map(functor, entries, self) { var that = self || null; for (var entry of entries) { var key = entry[0]; var value = entry[1]; yield [key, functor.call(that, value, key, entries)]; } } function * filter(predicate, entries, self) { var that = self || null; for (var entry of entries) { var key = entry[0]; var value = entry[1]; if (predicate.call(that, value, key, entries)) yield [key, value]; } } function toMap(entries) { var result = new Map; for (var entry of entries) { var key = entry[0]; var value = entry[1]; result.set(key, value); } return result; } function toArray(entries) { var array = []; for (var entry of entries) { array.push(entry[1]); } return array; } </script>

如果你想要一个更流畅的界面,你可以这样做:

var object = new Map; object.set("", "empty string"); object.set(0, "number zero"); object.set(object, "itself"); var result = new MapEntries(object).filter(primitive).map(appendBang).toMap(); alert(result.get("")); // empty string! alert(result.get(0)); // number zero! alert(result.get(object)); // undefined var array = new MapEntries(object).filter(primitive).map(appendBang).toArray(); alert(JSON.stringify(array, null, 4)); function primitive(value, key) { return isPrimitive(key); } function appendBang(value) { return value + "!"; } function isPrimitive(value) { var type = typeof value; return value === null || type !== "object" && type !== "function"; } <script> MapEntries.prototype = { constructor: MapEntries, map: function (functor, self) { return new MapEntries(map(functor, this.entries, self), true); }, filter: function (predicate, self) { return new MapEntries(filter(predicate, this.entries, self), true); }, toMap: function () { return toMap(this.entries); }, toArray: function () { return toArray(this.entries); } }; function MapEntries(map, entries) { this.entries = entries ? map : map.entries(); } function * map(functor, entries, self) { var that = self || null; for (var entry of entries) { var key = entry[0]; var value = entry[1]; yield [key, functor.call(that, value, key, entries)]; } } function * filter(predicate, entries, self) { var that = self || null; for (var entry of entries) { var key = entry[0]; var value = entry[1]; if (predicate.call(that, value, key, entries)) yield [key, value]; } } function toMap(entries) { var result = new Map; for (var entry of entries) { var key = entry[0]; var value = entry[1]; result.set(key, value); } return result; } function toArray(entries) { var array = []; for (var entry of entries) { array.push(entry[1]); } return array; } </script>

希望这能有所帮助。

你也可以使用fluent-iterable转换为数组:

const iterable: Iterable<T> = ...;
const arr: T[] = fluent(iterable).toArray();