【问题标题】:How can Method Chaining be done in JS?如何在 JS 中完成方法链?
【发布时间】:2016-03-09 10:16:12
【问题描述】:

我想链接这些方法调用:

utils.map([1,2,3,4,5], function (el) { return ++el; } )

utils.filter(function (el) {return !el%2; }

它们单独工作正常,但以下工作不正常。如何使以下代码工作?

utils
    .map([1,2,3,4,5], function (el) { return ++el; })
    .filter(function (el) { return !el%2; }

这是我的utils 对象:

var utils = {
    each: function (collection, iteratee){
        return collection.forEach(iteratee);
    },
    map: function (collection, iteratee) {
        return collection.map(iteratee);
    },
    filter: function (collection, predicate) {
        return collection.filter(predicate);
    },
    find: function (collection, predicate) {
        return collection.find(predicate);
    }
}

我知道当我链接两个方法时,参数会发生变化,我必须只提供迭代对象而不是集合。如何做到这一点?

提前致谢。如果需要,我愿意解释特定概念。

【问题讨论】:

  • 您找到有用的答案了吗?请发表评论,和/或接受其中一个答案。

标签: javascript methods method-chaining


【解决方案1】:

如果你想像这样链接函数,每个函数都需要返回“this”。

【讨论】:

  • 但是实际操作呢,对于 find 方法来说,我找到并返回键值对,如果我返回“this”,我将如何返回它。
  • 这是您的设计决定。如果您希望能够链接,这是一种非常有效的模式,您将失去返回“原始”结果的能力。您需要将结果放入 utils 的属性中,以便在完成时进行询问。或者您可以查看 Promise,其中一个步骤的输出成为下一步的输入。但是 promise 确实只对异步操作有意义。
【解决方案2】:

请注意,为了使forEach 可链接,您需要添加return 语句。如MDN docs on forEach

map()reduce() 不同,它总是返回未定义的值并且不可链接。典型的用例是在链的末端执行副作用。

其次,mapfilter 这样的原始方法已经适合链接。正如您在问题末尾已经提到的,您的方法要求第一个参数是方法的 subject,这是一种与链接不兼容的模式。使用链接,主体必须是公开方法的对象。

您可以通过每次通过链传递一个类似utils 的新对象来允许链接。要创建utils 的这些新实例,将其定义为函数对象更容易,因为这允许您使用new 关键字创建新的utils 对象。既然它真的是一个构造函数,那么Utils大写比较合适:

function Utils(collection) { // "constructor"
    this.collection = collection;
    this.each = function(iteratee){
        this.collection.forEach.call(this.collection, iteratee);
        return this;
    },
    this.map = function(iteratee){
        return new Utils(this.collection.map.call(this.collection, iteratee));
    },
    this.filter = function(predicate){
        return new Utils(this.collection.filter.call(this.collection, predicate));
    },
    this.find = function(predicate){
        return new Utils(this.collection.find.call(collection, predicate));
    }
};

// test it
result = new Utils([1,2,3,4,5])
          .map(function (el) { return ++el; })
          .filter(function (el) { return !(el%2); });

document.write(JSON.stringify(result.collection));

输出:

[2,4,6]

这里的Utils 是一个构造函数,它返回一个对象,该对象具有作用于私有集合属性的方法。每个方法都返回一个新的Utils 对象,forEach 的情况除外,因为它只能返回当前对象。

所以在这个脚本中,对map 的测试调用实际上是对Utils.map 的调用。

应该说这并没有比 map, filter, ... 可以链接的原生方式带来太多好处。但我想你还有其他计划来扩展这种模式。

次要注意:在表达式!el%2 中,您可能想先计算模运算符,然后计算!,但它执行的方向相反。所以我在上面的代码中加了括号。

【讨论】:

  • 这仅适用于 OP 想要在第二步中使用 Array 方法(而不是他的 utils 方法)。
  • 完全更新了答案,用 sn-p 演示了问题中给出的示例代码的链接。
【解决方案3】:

这是 trincot 响应的更新版本,采用 ES6 类设计。

class Collection {
  constructor(collection) {
    if (collection == null || !Array.isArray(collection)) {
      throw new Error('collection is null or not an array');
    }
    this.collection = collection;
  }
  each(iteratee) {
    this.collection.forEach.call(this.collection, iteratee);
    return this;
  }
  map(iteratee) {
    return new Collection(this.collection.map.call(this.collection, iteratee));
  }
  filter(predicate) {
    return new Collection(this.collection.filter.call(this.collection, predicate));
  }
  find(predicate) {
    return this.collection.find.call(this.collection, predicate);
  }
  contains(value) {
    return this.collection.includes.call(this.collection, value);
  }
  get() {
    return this.collection;
  }
}

const result = new Collection([1, 2, 3, 4, 5])
  .map(el => ++el)
  .filter(el => !(el % 2))
  .get();

console.log(result);

console.log(new Collection([1, 2, 3, 4, 5]).find(e => e === 2));
console.log(new Collection([1, 2, 3, 4, 5]).contains(2));
.as-console-wrapper { top: 0; max-height: 100% !important; }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多