【问题标题】:How to extend the array with functions that are generators and able to chain them?如何使用作为生成器并能够链接它们的函数来扩展数组?
【发布时间】:2019-05-24 12:03:33
【问题描述】:

如何使用返回生成器对象并能够链接它们的函数来扩展 Array?

我已经尝试扩展 Array 以查看是否可以链接生成器函数。第一个调用有效,但下一个链不会,因为它返回一个生成器对象,我不知道如何扩展它。

Array.prototype.select = function* (fn) {
    let it = this[Symbol.iterator]();
    for (let item of it) yield fn(item);
}

Array.prototype.where = function* (fn) {
    let it = this[Symbol.iterator]();
    for (let item of it) {
        if (fn(item)) yield item;
    }
}

我希望能够将这样的生成器链接到数组

let result = arr.select(v => v * 2)
                .where(v => v % 3 === 0);

for (let item of result) console.log(item);

【问题讨论】:

  • 为什么不使用内置方法? arr.map(v => v * 2).filter(v => v % 3 === 0)
  • 扩展原型是一个冒险的提议。见Why is extending native objects a bad practice?
  • @adiga 我是 javascript 新手,我正在尝试如何实现 LINQ 延迟执行。我只是对生成器感到好奇,因为它让我想起了 C# IEnumerator 接口。
  • @adiga checkout this repl 看看我想要实现什么。
  • @Kierkegaard 我已经完成了exactly this。它是用 TypeScript 编写的,但你可以在 node 中使用它,只使用 JavaScript,因为它是预编译的。我确实计划很快将其更新为 umd 格式。

标签: javascript generator chain


【解决方案1】:

在不修改Object 的情况下,您仍然可以使用我创造的"superclassing" 技术使这些方法可链接。

您首先定义Array 将扩展的基类,然后修改Array 的原型链以人为地扩展您的基类。

请注意 select()where() 方法如何封装您的原始生成器函数以返回该类的新实例,以便方法可链接。

class Enumerable {
  constructor (getIterator) {
    this[Symbol.iterator] = getIterator;
  }

  select (fn) {
    return new Enumerable(function * () {
      for (const value of this) {
        yield fn(value);
      }
    }.bind(this));
  }

  where (fn) {
    return new Enumerable(function * () {
      for (const value of this) {
        if (fn(value)) yield value;
      }
    }.bind(this));
  }
}

Object.setPrototypeOf(Array, Enumerable);
Object.setPrototypeOf(Array.prototype, Enumerable.prototype);

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const result = array
  .select(v => v * 2)
  .where(v => v % 3 === 0);

for (const item of result) {
  console.log(item);
}

【讨论】:

  • 太好了!我将使用这个实现。
【解决方案2】:

您可以将这两个方法存储为 Object 的原型函数并移交迭代器对象,因为这两个函数都采用生成器并返回一个可迭代对象。

Object.prototype.select = function* (fn) {
    for (let v of this) yield fn(v);
}

Object.prototype.where = function* (fn) {
    for (let v of this[Symbol.iterator]()) if (fn(v)) yield v;
}

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9],
    result = array[Symbol.iterator]()
        .select(v => v * 2)
        .where(v => v % 3 === 0);

console.log(...result);

【讨论】:

  • 哦,是的,忘记了对象,我太忙于将 c# 继承转换为我需要扩展的特定类。但是有没有更具体的方法?或者我不应该让它变得复杂?
【解决方案3】:

如果你不介意过度扩展,第一个迭代器只返回一个对象。 您可以通过console.log(typeof arr.select(v => v * 2)); 进行检查。

因此,您可以简单地定义:Object.prototype.where = function* (fn) {};

Array.prototype.select = function* (fn) {
    let it = this[Symbol.iterator]();
    for (let item of it) {
      yield fn(item);
    }
};

Object.prototype.where = function* (fn) {
    let it = this[Symbol.iterator]();
    for (let item of it) {
        if (fn(item)) yield item;
    }
};


const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let result = arr.select(v => v * 2)
                .where(v => v % 3 === 0);

for (let item of result) {
  console.log(item);
}

如果您希望顺序无关紧要,您可以像这样扩展 Array 和 Object。

Array.prototype.select = function* (fn) {
        let it = this[Symbol.iterator]();
        for (let item of it) {
          yield fn(item);
        }
    };

Array.prototype.where = function* (fn) {
    let it = this[Symbol.iterator]();
    for (let item of it) {
        if (fn(item)) yield item;
    }
};

Object.prototype.select = Array.prototype.select;
Object.prototype.where = Array.prototype.where;


const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

// Chain 1.
let result1 = arr.select(v => v * 2).where(v => v % 3 === 0);

console.log('Chain 1');
for (const item of result1) {
  console.log(item);
}

// Chain 2.
let result2 = arr.where(v => v % 3 === 0).select(v => v * 2);

console.log('Chain 2')
for (const item of result2) {
  console.log(item);
}

【讨论】:

  • 仅供参考,typeof 是一个运算符,而不是一个函数,所以你可以在没有括号的情况下使用 typeof arr.select(v => v * 2)
  • 好收获。更新。谢谢@HereticMonkey
猜你喜欢
  • 1970-01-01
  • 2017-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多