如何链接js函数?

Rad*_*dex 1 javascript ecmascript-6

让我们想象一下我有一个数字,我想执行一些算术运算示例。

x.add(3).subtract(2).print() // result 1
Run Code Online (Sandbox Code Playgroud)

或者

0.add(3).subtract(2).print() // result 1 (I have no code for this)
Run Code Online (Sandbox Code Playgroud)

目前我正在尝试以下代码。我想知道是否有更好更简洁的方法来达到相同的结果,

谢谢

x.add(3).subtract(2).print() // result 1
Run Code Online (Sandbox Code Playgroud)

T.J*_*der 5

这个问题几乎有两个不同的答案:

  • 一个超笼统的答案。

  • 适用于,呃,一般中的对象的一般答案。

  • 适用于数字的特定答案。

超一般答案

您可以在任何早期函数返回具有另一个函数作为属性(松散地,“方法”)的对象(或可以隐式强制为对象的对象)时链接函数。因此,例如,您可以链接.toString().toUpperCase()一个Date

var dt = new Date();
console.log(dt.toString().toUpperCase();
Run Code Online (Sandbox Code Playgroud)

...因为Date.prototype.toString返回一个字符串,而字符串有一个toUpperCase方法。

通用答案适用于,呃,通用中的对象

一种常见且有用的模式是让一个对象的方法返回对同一对象或同一“类型”的另一个对象的引用。(有时人们说“形状”而不是“类型”,因为 JavaScript 在很大程度上是无类型的。)例如,jQuery 使用这个效果很好。$()返回一个 jQuery 对象;根据您调用的方法,您将返回相同的 jQuery 对象(例如,each)或表示操作结果的新 jQuery 对象(例如,find)。这种模式非常有用,无论是对于可变对象,还是对于不可变对象。

在您自己的对象中执行此操作:仅返回相同的对象return this。要返回具有相同“形状”的新对象,您可以使用用于对象的任何构造机制来创建要返回的新对象。

因此,例如,如果我们在序列的开头添加一个函数来创建一个“流式”数字,我们要么使其可变并this每次都返回:

var dt = new Date();
console.log(dt.toString().toUpperCase();
Run Code Online (Sandbox Code Playgroud)

或者我们可以使nstreams 不可变,其中每个操作返回一个新的nstream

function nstream(val) {
  return {
    // Our add operation returns this same instance
    add: function(n) {
      val += n;
      return this;
    },
    // Same with multiply
    multiply: function(n) {
      val *= n;
      return this;
    },
    // To access the underlying value, we need an accessor
    result: function() {
      return val;
    },
    // This provides compatibility with built-in operations
    // such as + and *
    valueOf: function() {
      return val;
    }
  };
};
// Using only nstream ops:
console.log(nstream(1).add(3).multiply(4).result());
// Implicitly using valueOf at the end:
console.log(nstream(1).add(3) * 4);
Run Code Online (Sandbox Code Playgroud)

矛盾的是,像这样的不可变对象可以增加减少代码中的内存压力,这取决于它们是否可以帮助您避免防御性副本。例如,如果 annstream是另一个对象的成员并且你想知道它不能改变,如果它是可变的,你必须在将它分发给其他代码时制作一个防御性副本;如果它是不可变的,则不必这样做,但是在“修改”它时必须创建副本。但是不可变对象的成本和收益是一个有点问题的领域。:-)

适用于数字的特定答案

当您尝试对数字调用方法时,您必须将该方法添加到Number.prototype. 然后你会让你的方法(addmultiply等)返回操作的结果,这是一个数字将有你的其他方法。这是一个快速示例:

function nstream(val) {
  return {
    add: function(n) {
      return nstream(val + n);
    },
    // Same with multiply
    multiply: function(n) {
      return nstream(val * n);
    },
    // To access the underlying value, we need an accessor
    result: function() {
      return val;
    },
    // This provides compatibility with built-in operations
    // such as + and *
    valueOf: function() {
      return val;
    }
  };
};
// Using only nstream ops:
console.log(nstream(1).add(3).multiply(4).result());
// Implicitly using valueOf at the end:
console.log(nstream(1).add(3) * 4);
Run Code Online (Sandbox Code Playgroud)

对此有几点说明:

  • 与往常一样延伸内置的原型时,重要的是要使用Object.definePropertyObject.definePropertiesenumerable: true那么新的属性是不可枚举。(虽然对于数字来说,这并不是什么大问题。对于数组更是如此。我们完全不理会普通对象 [例如,Object.prototype]。)
  • ..1..add(3)可能看起来很奇怪,但它是你叫的数字文本方法的两种方法之一。第一个.是小数点。第二个.是属性访问器操作符。另一种方法是(1).add(3)因为数字文字的结束位置没有混淆。显然,这并没有带来变量:var n = 1; console.log(n.add(3).multiply(4));工作得很好。
  • Number 对象和数字原语之间有相当多的隐秘转换:当您访问原语(数字、字符串、布尔值)上的属性(包括函数属性)时,JavaScript 引擎会强制原语它的等效对象类型(数字、字符串、布尔值),然后在该对象上查找属性。因为它是一个新创建的对象,它唯一的属性来自它的原型。所以1..add(3)创建一个新的 Number 对象,然后调用add该对象。在add当我们这样做return this + n时,+强制转换的对象返回到其原始值。当然,所有这些都在 JavaScript 引擎可能和重要的地方进行了优化。